@paroicms/react-ui 0.4.4 → 0.5.1

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 (133) 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 +51 -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 +18 -0
  34. package/dist/MultiSelect.js +53 -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 +19 -0
  40. package/dist/PopupMenu.js +106 -0
  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 +25 -0
  52. package/dist/SplitButton.js +39 -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 +29 -0
  65. package/dist/Tree.d.ts +22 -0
  66. package/dist/Tree.js +43 -0
  67. package/dist/alert-stack.d.ts +20 -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/popup-positioning.d.ts +10 -0
  73. package/dist/popup-positioning.js +160 -0
  74. package/dist/react-ui-provider.d.ts +15 -0
  75. package/dist/react-ui-provider.js +22 -0
  76. package/package.json +16 -2
  77. package/styles/Accordion.css +46 -0
  78. package/styles/Alert.css +77 -0
  79. package/styles/Badge.css +59 -0
  80. package/styles/Breadcrumb.css +57 -0
  81. package/styles/Button.css +167 -0
  82. package/styles/Card.css +28 -0
  83. package/styles/Checkbox.css +61 -0
  84. package/styles/Chip.css +35 -0
  85. package/styles/DataTable.css +176 -0
  86. package/styles/DateInput.css +59 -0
  87. package/styles/Dialog.css +88 -0
  88. package/styles/Inplace.css +44 -0
  89. package/styles/InputNumber.css +60 -0
  90. package/styles/InputText.css +99 -0
  91. package/styles/MenuItem.css +169 -0
  92. package/styles/MultiSelect.css +143 -0
  93. package/styles/Panel.css +40 -0
  94. package/styles/PasswordInput.css +80 -0
  95. package/styles/PopupMenu.css +37 -0
  96. package/styles/RadioButton.css +60 -0
  97. package/styles/Select.css +72 -0
  98. package/styles/SideMenu.css +7 -0
  99. package/styles/SortableList.css +32 -0
  100. package/styles/Spinner.css +30 -0
  101. package/styles/SplitButton.css +137 -0
  102. package/styles/Switch.css +60 -0
  103. package/styles/Tabs.css +94 -0
  104. package/styles/Textarea.css +66 -0
  105. package/styles/ToggleButton.css +36 -0
  106. package/styles/ToggleGroup.css +55 -0
  107. package/styles/Tooltip.css +20 -0
  108. package/styles/Tree.css +162 -0
  109. package/styles/theme/base.css +40 -0
  110. package/styles/theme/common.css +410 -0
  111. package/styles/theme/index.css +15 -0
  112. package/styles/theme/margins.css +119 -0
  113. package/styles/theme/reset.css +119 -0
  114. package/styles/theme/tokens.css +226 -0
  115. package/dist/PuAccordion.d.ts +0 -9
  116. package/dist/PuButton.d.ts +0 -14
  117. package/dist/PuButton.jsx +0 -15
  118. package/dist/PuCheckbox.d.ts +0 -8
  119. package/dist/PuCheckbox.jsx +0 -13
  120. package/dist/PuInput.d.ts +0 -10
  121. package/dist/PuInput.jsx +0 -13
  122. package/dist/PuMenuItem.d.ts +0 -7
  123. package/dist/PuMenuItem.jsx +0 -33
  124. package/dist/PuPopupMenu.d.ts +0 -14
  125. package/dist/PuPopupMenu.jsx +0 -135
  126. package/dist/PuSelect.d.ts +0 -17
  127. package/dist/PuSelect.jsx +0 -24
  128. package/dist/PuSideMenu.d.ts +0 -4
  129. package/dist/PuSideMenu.jsx +0 -15
  130. package/dist/PuSpinner.d.ts +0 -1
  131. package/dist/PuSpinner.jsx +0 -3
  132. package/dist/svg-icons.d.ts +0 -5
  133. package/dist/svg-icons.jsx +0 -30
package/dist/Tree.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { type ReactNode } from "react";
2
+ import "../styles/Tree.css";
3
+ export interface TreeNode {
4
+ key: string;
5
+ label: ReactNode;
6
+ children?: TreeNode[];
7
+ data?: unknown;
8
+ selectable?: boolean;
9
+ icon?: ReactNode;
10
+ className?: string;
11
+ }
12
+ export interface TreeProps {
13
+ value: TreeNode[];
14
+ selectionMode?: "single" | "checkbox";
15
+ selection?: Record<string, boolean>;
16
+ onSelectionChange?: (selection: Record<string, boolean>) => void;
17
+ expandedKeys?: Record<string, boolean>;
18
+ onExpandedKeysChange?: (keys: Record<string, boolean>) => void;
19
+ nodeTemplate?: (node: TreeNode) => ReactNode;
20
+ className?: string;
21
+ }
22
+ export declare function Tree({ value, selectionMode, selection, onSelectionChange, expandedKeys, onExpandedKeysChange, nodeTemplate, className, }: TreeProps): import("react/jsx-runtime").JSX.Element;
package/dist/Tree.js ADDED
@@ -0,0 +1,43 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { clsx } from "clsx";
3
+ import { ChevronDown, ChevronRight } from "lucide-react";
4
+ import { useCallback } from "react";
5
+ import "../styles/Tree.css";
6
+ export function Tree({ value, selectionMode, selection = {}, onSelectionChange, expandedKeys = {}, onExpandedKeysChange, nodeTemplate, className, }) {
7
+ const toggleExpand = useCallback((key) => {
8
+ if (onExpandedKeysChange) {
9
+ const newKeys = { ...expandedKeys };
10
+ if (newKeys[key]) {
11
+ delete newKeys[key];
12
+ }
13
+ else {
14
+ newKeys[key] = true;
15
+ }
16
+ onExpandedKeysChange(newKeys);
17
+ }
18
+ }, [expandedKeys, onExpandedKeysChange]);
19
+ const toggleSelect = useCallback((key, node) => {
20
+ if (!onSelectionChange || node.selectable === false)
21
+ return;
22
+ if (selectionMode === "single") {
23
+ onSelectionChange({ [key]: true });
24
+ }
25
+ else if (selectionMode === "checkbox") {
26
+ const newSelection = { ...selection };
27
+ if (newSelection[key]) {
28
+ delete newSelection[key];
29
+ }
30
+ else {
31
+ newSelection[key] = true;
32
+ }
33
+ onSelectionChange(newSelection);
34
+ }
35
+ }, [selection, selectionMode, onSelectionChange]);
36
+ return (_jsx("div", { className: clsx("PaTree", className), children: _jsx("ul", { className: "PaTree-root", children: value.map((node) => (_jsx(TreeNodeItem, { node: node, expandedKeys: expandedKeys, selection: selection, selectionMode: selectionMode, onToggleExpand: toggleExpand, onToggleSelect: toggleSelect, nodeTemplate: nodeTemplate }, node.key))) }) }));
37
+ }
38
+ function TreeNodeItem({ node, expandedKeys, selection, selectionMode, onToggleExpand, onToggleSelect, nodeTemplate, }) {
39
+ const hasChildren = node.children && node.children.length > 0;
40
+ const isExpanded = expandedKeys[node.key];
41
+ const isSelected = selection[node.key];
42
+ return (_jsxs("li", { className: clsx("PaTree-node", node.className), children: [_jsxs("div", { className: clsx("PaTree-nodeContent", isSelected && "selected"), children: [hasChildren ? (_jsx("button", { type: "button", className: "PaTree-toggle", onClick: () => onToggleExpand(node.key), "aria-label": isExpanded ? "Collapse" : "Expand", children: isExpanded ? _jsx(ChevronDown, { size: 14 }) : _jsx(ChevronRight, { size: 14 }) })) : (_jsx("span", { className: "PaTree-togglePlaceholder" })), selectionMode === "checkbox" && (_jsx("input", { type: "checkbox", className: "PaTree-checkbox", checked: isSelected, onChange: () => onToggleSelect(node.key, node), disabled: node.selectable === false })), selectionMode === "single" && node.selectable !== false ? (_jsxs("button", { type: "button", className: clsx("PaTree-labelWrapper", "selectable"), onClick: () => onToggleSelect(node.key, node), children: [node.icon && _jsx("span", { className: "PaTree-icon", children: node.icon }), nodeTemplate ? nodeTemplate(node) : _jsx("span", { className: "PaTree-label", children: node.label })] })) : (_jsxs("div", { className: "PaTree-labelWrapper", children: [node.icon && _jsx("span", { className: "PaTree-icon", children: node.icon }), nodeTemplate ? nodeTemplate(node) : _jsx("span", { className: "PaTree-label", children: node.label })] }))] }), hasChildren && isExpanded && (_jsx("ul", { className: "PaTree-children", children: node.children?.map((child) => (_jsx(TreeNodeItem, { node: child, expandedKeys: expandedKeys, selection: selection, selectionMode: selectionMode, onToggleExpand: onToggleExpand, onToggleSelect: onToggleSelect, nodeTemplate: nodeTemplate }, child.key))) }))] }));
43
+ }
@@ -0,0 +1,20 @@
1
+ import type { ReactNode, Ref } from "react";
2
+ export type AlertSeverity = "info" | "success" | "warning" | "error";
3
+ export interface ShowAlertOptions {
4
+ severity?: AlertSeverity;
5
+ autoDismiss?: boolean;
6
+ }
7
+ export interface AlertStackHandle {
8
+ showAlert: (message: string, options?: ShowAlertOptions) => void;
9
+ showError: (error: unknown) => void;
10
+ }
11
+ interface AlertStackProviderProps {
12
+ children: ReactNode;
13
+ ref?: Ref<AlertStackHandle>;
14
+ }
15
+ export declare function AlertStackProvider({ children, ref }: AlertStackProviderProps): import("react/jsx-runtime").JSX.Element;
16
+ export declare function useAlertStack(): AlertStackHandle;
17
+ export declare function AlertStack(props?: {
18
+ className?: string;
19
+ }): import("react/jsx-runtime").JSX.Element | null;
20
+ export {};
@@ -0,0 +1,72 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { messageOf } from "@paroicms/public-anywhere-lib";
3
+ import { createContext, useCallback, useContext, useEffect, useImperativeHandle, useReducer, useRef, } from "react";
4
+ import { Alert } from "./Alert.js";
5
+ import { useReactUIConfig } from "./react-ui-provider.js";
6
+ const AlertStackContext = createContext(undefined);
7
+ export function AlertStackProvider({ children, ref }) {
8
+ const { logger } = useReactUIConfig();
9
+ const queue = useRef([]);
10
+ const idSeq = useRef(0);
11
+ const [renderCount, forceRender] = useReducer((x) => x + 1, 0);
12
+ const showAlert = useCallback((message, options) => {
13
+ const alert = {
14
+ id: ++idSeq.current,
15
+ message,
16
+ severity: options?.severity ?? "info",
17
+ autoDismiss: options?.autoDismiss ?? false,
18
+ };
19
+ queue.current.push(alert);
20
+ forceRender();
21
+ }, []);
22
+ const dismissCurrent = useCallback(() => {
23
+ queue.current.shift();
24
+ forceRender();
25
+ }, []);
26
+ const showError = useCallback((error) => {
27
+ logger.error("AlertStack caught error:", error);
28
+ showAlert(messageOf(error), { severity: "error" });
29
+ }, [showAlert]);
30
+ useImperativeHandle(ref, () => ({ showAlert, showError }), [showAlert, showError]);
31
+ const contextValue = {
32
+ showAlert,
33
+ queue: queue.current,
34
+ dismissCurrent,
35
+ };
36
+ // renderCount is used to ensure context value updates trigger re-renders
37
+ void renderCount;
38
+ return _jsx(AlertStackContext.Provider, { value: contextValue, children: children });
39
+ }
40
+ export function useAlertStack() {
41
+ const context = useContext(AlertStackContext);
42
+ if (!context) {
43
+ throw new Error("useAlertStack must be used within an AlertStackProvider");
44
+ }
45
+ const showError = useCallback((error) => {
46
+ context.showAlert(messageOf(error), { severity: "error" });
47
+ }, [context.showAlert]);
48
+ return { showAlert: context.showAlert, showError };
49
+ }
50
+ export function AlertStack(props) {
51
+ const context = useContext(AlertStackContext);
52
+ const config = useReactUIConfig();
53
+ if (!context) {
54
+ throw new Error("AlertStack must be used within an AlertStackProvider");
55
+ }
56
+ const { queue, dismissCurrent } = context;
57
+ const currentAlert = queue[0];
58
+ useEffect(() => {
59
+ if (!currentAlert?.autoDismiss)
60
+ return;
61
+ const timer = setTimeout(dismissCurrent, config.autoDismissAlertDurationMs);
62
+ return () => clearTimeout(timer);
63
+ }, [
64
+ currentAlert?.id,
65
+ currentAlert?.autoDismiss,
66
+ config.autoDismissAlertDurationMs,
67
+ dismissCurrent,
68
+ ]);
69
+ if (!currentAlert)
70
+ return null;
71
+ return (_jsx(Alert, { className: props?.className, severity: currentAlert.severity, onClose: dismissCurrent, children: currentAlert.message }));
72
+ }
package/dist/index.d.ts CHANGED
@@ -1,11 +1,37 @@
1
- import "../styles/index.scss";
2
- export * from "./PuAccordion.jsx";
3
- export * from "./PuButton.jsx";
4
- export * from "./PuCheckbox.jsx";
5
- export * from "./PuInput.jsx";
6
- export * from "./PuMenuItem.jsx";
7
- export * from "./PuPopupMenu.jsx";
8
- export * from "./PuSelect.jsx";
9
- export * from "./PuSideMenu.jsx";
10
- export * from "./PuSpinner.jsx";
1
+ import "../styles/theme/index.css";
2
+ export * from "./Checkbox.js";
3
+ export * from "./DateInput.js";
4
+ export * from "./InputNumber.js";
5
+ export * from "./InputText.js";
6
+ export * from "./MultiSelect.js";
7
+ export * from "./PasswordInput.js";
8
+ export * from "./RadioButton.js";
9
+ export * from "./Select.js";
10
+ export * from "./Switch.js";
11
+ export * from "./Textarea.js";
12
+ export * from "./ToggleGroup.js";
13
+ export * from "./Button.js";
14
+ export * from "./SplitButton.js";
15
+ export * from "./ToggleButton.js";
16
+ export * from "./Alert.js";
17
+ export * from "./Badge.js";
18
+ export * from "./Card.js";
19
+ export * from "./Chip.js";
20
+ export * from "./Column.js";
21
+ export * from "./DataTable.js";
22
+ export * from "./Breadcrumb.js";
23
+ export * from "./MenuItem.js";
24
+ export * from "./PopupMenu.js";
25
+ export * from "./SideMenu.js";
26
+ export * from "./Tabs.js";
27
+ export * from "./Dialog.js";
28
+ export * from "./Spinner.js";
29
+ export * from "./Tooltip.js";
30
+ export * from "./Accordion.js";
31
+ export * from "./Inplace.js";
32
+ export * from "./Panel.js";
33
+ export * from "./Tree.js";
34
+ export * from "./SortableList.js";
35
+ export * from "./alert-stack.js";
36
+ export * from "./react-ui-provider.js";
11
37
  export type * from "./paroi-ui-lib-types.js";
package/dist/index.js CHANGED
@@ -1,10 +1,45 @@
1
- import "../styles/index.scss";
2
- export * from "./PuAccordion.jsx";
3
- export * from "./PuButton.jsx";
4
- export * from "./PuCheckbox.jsx";
5
- export * from "./PuInput.jsx";
6
- export * from "./PuMenuItem.jsx";
7
- export * from "./PuPopupMenu.jsx";
8
- export * from "./PuSelect.jsx";
9
- export * from "./PuSideMenu.jsx";
10
- export * from "./PuSpinner.jsx";
1
+ import "../styles/theme/index.css";
2
+ // Form Controls
3
+ export * from "./Checkbox.js";
4
+ export * from "./DateInput.js";
5
+ export * from "./InputNumber.js";
6
+ export * from "./InputText.js";
7
+ export * from "./MultiSelect.js";
8
+ export * from "./PasswordInput.js";
9
+ export * from "./RadioButton.js";
10
+ export * from "./Select.js";
11
+ export * from "./Switch.js";
12
+ export * from "./Textarea.js";
13
+ export * from "./ToggleGroup.js";
14
+ // Buttons
15
+ export * from "./Button.js";
16
+ export * from "./SplitButton.js";
17
+ export * from "./ToggleButton.js";
18
+ // Data Display
19
+ export * from "./Alert.js";
20
+ export * from "./Badge.js";
21
+ export * from "./Card.js";
22
+ export * from "./Chip.js";
23
+ export * from "./Column.js";
24
+ export * from "./DataTable.js";
25
+ // Navigation
26
+ export * from "./Breadcrumb.js";
27
+ export * from "./MenuItem.js";
28
+ export * from "./PopupMenu.js";
29
+ export * from "./SideMenu.js";
30
+ export * from "./Tabs.js";
31
+ // Overlays & Feedback
32
+ export * from "./Dialog.js";
33
+ export * from "./Spinner.js";
34
+ export * from "./Tooltip.js";
35
+ // Layout
36
+ export * from "./Accordion.js";
37
+ export * from "./Inplace.js";
38
+ export * from "./Panel.js";
39
+ // Tree
40
+ export * from "./Tree.js";
41
+ // Drag & Drop
42
+ export * from "./SortableList.js";
43
+ // Providers
44
+ export * from "./alert-stack.js";
45
+ export * from "./react-ui-provider.js";
@@ -1,5 +1,5 @@
1
1
  import type { CSSProperties, ReactNode } from "react";
2
- export interface PuMenuOption {
2
+ export interface MenuOption {
3
3
  /**
4
4
  * Unique identifier of the menuitem.
5
5
  */
@@ -32,14 +32,14 @@ export interface PuMenuOption {
32
32
  * Callback to execute when item is clicked.
33
33
  */
34
34
  command?(): void;
35
- popupMenu?: PuMenuNode[];
35
+ popupMenu?: MenuNode[];
36
36
  active?: boolean;
37
37
  }
38
- export interface PuMenuNode extends PuMenuOption {
38
+ export interface MenuNode extends MenuOption {
39
39
  /**
40
40
  * An array of child menu items.
41
41
  */
42
- subMenu?: PuMenuNode[];
42
+ subMenu?: MenuNode[];
43
43
  /**
44
44
  * Initial visibility of submenu.
45
45
  */
@@ -0,0 +1,10 @@
1
+ export type CornerPosition = "topLeft" | "topRight" | "bottomLeft" | "bottomRight";
2
+ export type VerticalPosition = "top" | "bottom";
3
+ export type CardinalPosition = "top" | "bottom" | "left" | "right";
4
+ export interface PositionResult {
5
+ top: number;
6
+ left: number;
7
+ position: string;
8
+ }
9
+ export declare function setupPopoverPositioning(popupEl: HTMLElement, getPosition: () => PositionResult): () => void;
10
+ export declare function computePopupPosition(triggerEl: HTMLElement, popupEl: HTMLElement, preferredPosition: string, mode: "corner" | "vertical" | "cardinal"): PositionResult;
@@ -0,0 +1,160 @@
1
+ export function setupPopoverPositioning(popupEl, getPosition) {
2
+ const handleToggle = (e) => {
3
+ const newState = e.newState;
4
+ if (newState === "open") {
5
+ const position = getPosition();
6
+ popupEl.style.position = "absolute";
7
+ popupEl.style.top = `${position.top}px`;
8
+ popupEl.style.left = `${position.left}px`;
9
+ }
10
+ };
11
+ popupEl.addEventListener("toggle", handleToggle);
12
+ return () => popupEl.removeEventListener("toggle", handleToggle);
13
+ }
14
+ export function computePopupPosition(triggerEl, popupEl, preferredPosition, mode) {
15
+ const triggerRect = triggerEl.getBoundingClientRect();
16
+ const popupWidth = popupEl.offsetWidth;
17
+ const popupHeight = popupEl.offsetHeight;
18
+ const bounds = getConstrainingBounds(triggerEl);
19
+ let result;
20
+ switch (mode) {
21
+ case "corner":
22
+ result = computeCornerPosition(triggerRect, popupWidth, popupHeight, bounds, preferredPosition);
23
+ break;
24
+ case "vertical":
25
+ result = computeVerticalPosition(triggerRect, popupWidth, popupHeight, bounds, preferredPosition);
26
+ break;
27
+ case "cardinal":
28
+ result = computeCardinalPosition(triggerRect, popupWidth, popupHeight, bounds, preferredPosition);
29
+ break;
30
+ }
31
+ return {
32
+ position: result.position,
33
+ top: result.top + window.scrollY,
34
+ left: result.left + window.scrollX,
35
+ };
36
+ }
37
+ function getConstrainingBounds(triggerEl) {
38
+ const dialog = triggerEl.closest("dialog");
39
+ if (dialog) {
40
+ const rect = dialog.getBoundingClientRect();
41
+ return {
42
+ top: rect.top,
43
+ left: rect.left,
44
+ right: rect.right,
45
+ bottom: rect.bottom,
46
+ };
47
+ }
48
+ return {
49
+ top: 0,
50
+ left: 0,
51
+ right: window.innerWidth,
52
+ bottom: window.innerHeight,
53
+ };
54
+ }
55
+ function computeCornerPosition(triggerRect, popupWidth, popupHeight, bounds, preferredPosition) {
56
+ const spaceTop = triggerRect.top - bounds.top;
57
+ const spaceBottom = bounds.bottom - triggerRect.bottom;
58
+ const spaceLeft = triggerRect.left - bounds.left;
59
+ const spaceRight = bounds.right - triggerRect.right;
60
+ let position;
61
+ if (preferredPosition !== "auto") {
62
+ position = preferredPosition;
63
+ }
64
+ else {
65
+ // Pick corner with most space, prefer bottom-right > bottom-left > top-right > top-left
66
+ const canBottom = spaceBottom >= popupHeight;
67
+ const canTop = spaceTop >= popupHeight;
68
+ const canRight = spaceRight + triggerRect.width >= popupWidth;
69
+ const canLeft = spaceLeft + triggerRect.width >= popupWidth;
70
+ if (canBottom && canRight) {
71
+ position = "bottomRight";
72
+ }
73
+ else if (canBottom && canLeft) {
74
+ position = "bottomLeft";
75
+ }
76
+ else if (canTop && canRight) {
77
+ position = "topRight";
78
+ }
79
+ else if (canTop && canLeft) {
80
+ position = "topLeft";
81
+ }
82
+ else {
83
+ // Fallback: choose based on available space
84
+ const vertical = spaceBottom >= spaceTop ? "bottom" : "top";
85
+ const horizontal = spaceRight >= spaceLeft ? "Right" : "Left";
86
+ position = `${vertical}${horizontal}`;
87
+ }
88
+ }
89
+ let top;
90
+ let left;
91
+ if (position.startsWith("bottom")) {
92
+ top = triggerRect.bottom;
93
+ }
94
+ else {
95
+ top = triggerRect.top - popupHeight;
96
+ }
97
+ if (position.endsWith("Right")) {
98
+ left = triggerRect.left;
99
+ }
100
+ else {
101
+ left = triggerRect.right - popupWidth;
102
+ }
103
+ return { position, top, left };
104
+ }
105
+ function computeVerticalPosition(triggerRect, _popupWidth, popupHeight, bounds, preferredPosition) {
106
+ const spaceTop = triggerRect.top - bounds.top;
107
+ const spaceBottom = bounds.bottom - triggerRect.bottom;
108
+ let position;
109
+ if (preferredPosition !== "auto") {
110
+ position = preferredPosition;
111
+ }
112
+ else {
113
+ position = spaceBottom >= popupHeight || spaceBottom >= spaceTop ? "bottom" : "top";
114
+ }
115
+ const top = position === "bottom" ? triggerRect.bottom : triggerRect.top - popupHeight;
116
+ const left = triggerRect.left;
117
+ return { position, top, left };
118
+ }
119
+ function computeCardinalPosition(triggerRect, popupWidth, popupHeight, bounds, preferredPosition) {
120
+ const spaceTop = triggerRect.top - bounds.top;
121
+ const spaceBottom = bounds.bottom - triggerRect.bottom;
122
+ const spaceLeft = triggerRect.left - bounds.left;
123
+ const spaceRight = bounds.right - triggerRect.right;
124
+ let position;
125
+ if (preferredPosition !== "auto") {
126
+ position = preferredPosition;
127
+ }
128
+ else {
129
+ // Pick direction with most space
130
+ const spaces = [
131
+ { dir: "bottom", space: spaceBottom },
132
+ { dir: "right", space: spaceRight },
133
+ { dir: "top", space: spaceTop },
134
+ { dir: "left", space: spaceLeft },
135
+ ];
136
+ spaces.sort((a, b) => b.space - a.space);
137
+ position = spaces[0].dir;
138
+ }
139
+ let top;
140
+ let left;
141
+ switch (position) {
142
+ case "top":
143
+ top = triggerRect.top - popupHeight;
144
+ left = triggerRect.left + (triggerRect.width - popupWidth) / 2;
145
+ break;
146
+ case "bottom":
147
+ top = triggerRect.bottom;
148
+ left = triggerRect.left + (triggerRect.width - popupWidth) / 2;
149
+ break;
150
+ case "left":
151
+ top = triggerRect.top + (triggerRect.height - popupHeight) / 2;
152
+ left = triggerRect.left - popupWidth;
153
+ break;
154
+ case "right":
155
+ top = triggerRect.top + (triggerRect.height - popupHeight) / 2;
156
+ left = triggerRect.right;
157
+ break;
158
+ }
159
+ return { position, top, left };
160
+ }
@@ -0,0 +1,15 @@
1
+ import type { ReactNode } from "react";
2
+ export interface ReactUIConfig {
3
+ autoDismissAlertDurationMs?: number;
4
+ autoDismissInsideFeedbackDurationMs?: number;
5
+ logger?: {
6
+ error: (...messages: unknown[]) => void;
7
+ };
8
+ }
9
+ interface ReactUIProviderProps {
10
+ config?: ReactUIConfig;
11
+ children: ReactNode;
12
+ }
13
+ export declare function ReactUIProvider({ config, children }: ReactUIProviderProps): import("react/jsx-runtime").JSX.Element;
14
+ export declare function useReactUIConfig(): Required<ReactUIConfig>;
15
+ export {};
@@ -0,0 +1,22 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { createContext, useContext } from "react";
3
+ const ReactUIContext = createContext(undefined);
4
+ const defaultConfig = {
5
+ autoDismissAlertDurationMs: 5000,
6
+ autoDismissInsideFeedbackDurationMs: 2000,
7
+ logger: {
8
+ error: console.error,
9
+ },
10
+ };
11
+ export function ReactUIProvider({ config, children }) {
12
+ return _jsx(ReactUIContext.Provider, { value: config, children: children });
13
+ }
14
+ export function useReactUIConfig() {
15
+ const config = useContext(ReactUIContext);
16
+ return {
17
+ autoDismissAlertDurationMs: config?.autoDismissAlertDurationMs ?? defaultConfig.autoDismissAlertDurationMs,
18
+ autoDismissInsideFeedbackDurationMs: config?.autoDismissInsideFeedbackDurationMs ??
19
+ defaultConfig.autoDismissInsideFeedbackDurationMs,
20
+ logger: config?.logger ?? defaultConfig.logger,
21
+ };
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paroicms/react-ui",
3
- "version": "0.4.4",
3
+ "version": "0.5.1",
4
4
  "description": "React UI toolkit for ParoiCMS.",
5
5
  "keywords": [
6
6
  "paroicms",
@@ -19,19 +19,33 @@
19
19
  "main": "dist/index.js",
20
20
  "typings": "dist/index.d.ts",
21
21
  "scripts": {
22
+ "tsc": "tsc",
22
23
  "build": "tsc",
23
24
  "clear": "rimraf dist/*",
24
25
  "dev": "tsc --watch --preserveWatchOutput"
25
26
  },
27
+ "dependencies": {
28
+ "@paroicms/public-anywhere-lib": "0.41.0",
29
+ "clsx": "~2.1.1",
30
+ "lucide-react": "~0.562.0",
31
+ "react-sortablejs": "~6.1.4",
32
+ "sortablejs": "~1.15.6"
33
+ },
34
+ "peerDependencies": {
35
+ "react": ">=18.0.0",
36
+ "react-dom": ">=18.0.0"
37
+ },
26
38
  "devDependencies": {
27
39
  "@types/react": "~19.2.7",
28
40
  "@types/react-dom": "~19.2.3",
41
+ "@types/sortablejs": "^1.15.8",
29
42
  "react": "~19.2.0",
30
43
  "react-dom": "~19.2.0",
31
44
  "rimraf": "~6.1.2",
32
45
  "typescript": "~5.9.3"
33
46
  },
34
47
  "files": [
35
- "dist"
48
+ "dist",
49
+ "styles"
36
50
  ]
37
51
  }
@@ -0,0 +1,46 @@
1
+ /* ========================================
2
+ Accordion / Collapsible
3
+ ======================================== */
4
+ .PaAccordion-content {
5
+ padding-left: 20px;
6
+ transition: max-height 200ms ease-out;
7
+
8
+ &.transition {
9
+ overflow: hidden;
10
+ }
11
+
12
+ &.hide {
13
+ display: none;
14
+ }
15
+ }
16
+
17
+ .PaAccordion-header {
18
+ display: flex;
19
+ gap: var(--space-3);
20
+ align-items: center;
21
+ justify-content: space-between;
22
+ padding: var(--space-4) var(--space-5);
23
+ cursor: pointer;
24
+ background: var(--color-bg-subtle);
25
+ transition: background-color var(--transition);
26
+
27
+ /* &:hover {
28
+ background: var(--color-bg-section);
29
+ } */
30
+ }
31
+
32
+ .PaAccordion-title {
33
+ font-size: var(--text-base);
34
+ font-weight: var(--font-medium);
35
+ color: var(--color-text);
36
+ }
37
+
38
+ .PaAccordion-chevron {
39
+ flex-shrink: 0;
40
+ color: var(--color-text-light);
41
+ transition: transform var(--transition);
42
+
43
+ .PaAccordion.open & {
44
+ transform: rotate(180deg);
45
+ }
46
+ }
@@ -0,0 +1,77 @@
1
+ /* ========================================
2
+ Alert / Notice Box
3
+ ======================================== */
4
+ .PaAlert {
5
+ position: relative;
6
+ padding: var(--space-4) var(--space-5);
7
+ margin: var(--space-4) 0;
8
+ border-left: 4px solid;
9
+ border-radius: var(--radius);
10
+
11
+ &.dismissible {
12
+ padding-right: var(--space-10);
13
+ }
14
+
15
+ &.info {
16
+ background: var(--color-info-light);
17
+ border-color: var(--color-info);
18
+ }
19
+
20
+ &.primary {
21
+ background: var(--color-primary-light);
22
+ border-color: var(--color-primary);
23
+ }
24
+
25
+ &.success {
26
+ background: var(--color-success-light);
27
+ border-color: var(--color-success);
28
+ }
29
+
30
+ &.warning {
31
+ background: var(--color-yellow-light);
32
+ border-color: var(--color-yellow);
33
+ }
34
+
35
+ &.error {
36
+ background: var(--color-danger-light);
37
+ border-color: var(--color-danger);
38
+ }
39
+ }
40
+
41
+ .PaAlert-closeBtn {
42
+ position: absolute;
43
+ top: var(--space-3);
44
+ right: var(--space-3);
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: center;
48
+ padding: var(--space-1);
49
+ color: var(--color-text-muted);
50
+ cursor: pointer;
51
+ background: transparent;
52
+ border: none;
53
+ border-radius: var(--radius-sm);
54
+ transition:
55
+ color 0.15s,
56
+ background-color 0.15s;
57
+
58
+ &:hover {
59
+ color: var(--color-text);
60
+ background: rgb(0 0 0 / 10%);
61
+ }
62
+ }
63
+
64
+ .PaAlert-title {
65
+ margin-bottom: var(--space-2);
66
+ font-size: var(--text-sm);
67
+ font-weight: var(--font-semibold);
68
+ color: var(--color-text);
69
+ text-transform: uppercase;
70
+ letter-spacing: 0.025em;
71
+ }
72
+
73
+ .PaAlert-content {
74
+ font-size: var(--text-sm);
75
+ line-height: 1.7;
76
+ color: var(--color-text);
77
+ }