@zauru-sdk/components 1.0.113 → 1.0.115

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -3,6 +3,22 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.0.115](https://github.com/intuitiva/zauru-typescript-sdk/compare/v1.0.114...v1.0.115) (2024-09-26)
7
+
8
+ **Note:** Version bump only for package @zauru-sdk/components
9
+
10
+
11
+
12
+
13
+
14
+ ## [1.0.114](https://github.com/intuitiva/zauru-typescript-sdk/compare/v1.0.113...v1.0.114) (2024-09-25)
15
+
16
+ **Note:** Version bump only for package @zauru-sdk/components
17
+
18
+
19
+
20
+
21
+
6
22
  ## [1.0.113](https://github.com/intuitiva/zauru-typescript-sdk/compare/v1.0.112...v1.0.113) (2024-09-25)
7
23
 
8
24
  **Note:** Version bump only for package @zauru-sdk/components
@@ -7,6 +7,7 @@ type Props = {
7
7
  onSubmit?: SubmitHandler<FieldValues>;
8
8
  id?: string;
9
9
  method?: "post" | "put" | "delete" | "patch";
10
+ className?: string;
10
11
  };
11
12
  export declare const ReactZodForm: (props: Props) => import("react/jsx-runtime").JSX.Element;
12
13
  export default ReactZodForm;
@@ -0,0 +1,21 @@
1
+ import React, { ReactNode } from "react";
2
+ /**
3
+ * SidePanel Component
4
+ *
5
+ * This component creates a collapsible side panel that can be toggled open and closed.
6
+ *
7
+ * @param {ReactNode} children - The content to be displayed inside the side panel.
8
+ * @param {boolean} [closeOnClickOutside=true] - If true, the panel will close when clicking outside of it.
9
+ * @param {number} [widthPercentage=25] - The width of the panel as a percentage of the viewport width.
10
+ * @param {string} [buttonIcon="chevron"] - The icon to use for the toggle button. Can be "chevron", "filter", or a custom ReactNode.
11
+ *
12
+ * @returns A toggleable side panel component.
13
+ */
14
+ interface SidePanelProps {
15
+ children: ReactNode;
16
+ closeOnClickOutside?: boolean;
17
+ widthPercentage?: number;
18
+ buttonIcon?: "chevron" | "filter" | ReactNode;
19
+ }
20
+ declare const SidePanel: React.FC<SidePanelProps>;
21
+ export default SidePanel;
@@ -26,7 +26,7 @@ const FormDatePicker = (props) => {
26
26
  setValue("");
27
27
  onChange && onChange("");
28
28
  };
29
- return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [title && ((0, jsx_runtime_1.jsxs)("label", { htmlFor: error ? `${name}-error` : `${name}-success`, className: `block text-sm font-medium ${textColor} ${className}`, children: [title, required && (0, jsx_runtime_1.jsx)("span", { className: "text-red-500", children: "*" })] })), (0, jsx_runtime_1.jsxs)("div", { className: "flex relative items-center", children: [(0, jsx_runtime_1.jsx)("div", { className: "absolute left-0 flex items-center pl-3 pointer-events-none", children: (0, jsx_runtime_1.jsx)(icons_1.CalendarIconSVG, {}) }), (0, jsx_runtime_1.jsx)("input", { id: id, tabIndex: tabIndex, type: "date", value: value ?? "", pattern: "\\d{4}-\\d{2}-\\d{2}", className: `${bgColor} ${borderColor} ${textColor} text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 p-2.5`, ...(register ?? {}), name: name, onChange: (e) => {
29
+ return ((0, jsx_runtime_1.jsxs)("div", { children: [title && ((0, jsx_runtime_1.jsxs)("label", { htmlFor: error ? `${name}-error` : `${name}-success`, className: `block text-sm font-medium ${textColor} ${className}`, children: [title, required && (0, jsx_runtime_1.jsx)("span", { className: "text-red-500", children: "*" })] })), (0, jsx_runtime_1.jsxs)("div", { className: "flex relative items-center", children: [(0, jsx_runtime_1.jsx)("div", { className: "absolute left-0 flex items-center pl-3 pointer-events-none", children: (0, jsx_runtime_1.jsx)(icons_1.CalendarIconSVG, {}) }), (0, jsx_runtime_1.jsx)("input", { id: id, tabIndex: tabIndex, type: "date", value: value ?? "", pattern: "\\d{4}-\\d{2}-\\d{2}", className: `${bgColor} ${borderColor} ${textColor} text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 p-2.5`, ...(register ?? {}), name: name, onChange: (e) => {
30
30
  setValue(e.target.value);
31
31
  onChange && onChange(e.target.value);
32
32
  if (register) {
@@ -8,7 +8,7 @@ const react_hook_form_1 = require("react-hook-form");
8
8
  const zod_2 = require("zod");
9
9
  const emptySchema = zod_2.z.any();
10
10
  const ReactZodForm = (props) => {
11
- const { children, method = "post", schema = emptySchema, onSubmit, id, } = props;
11
+ const { children, method = "post", schema = emptySchema, onSubmit, id, className, } = props;
12
12
  const submit = (0, react_1.useSubmit)();
13
13
  const methods = (0, react_hook_form_1.useForm)({
14
14
  resolver: (0, zod_1.zodResolver)(schema),
@@ -23,7 +23,7 @@ const ReactZodForm = (props) => {
23
23
  submit(event?.target, { method });
24
24
  }
25
25
  };
26
- return ((0, jsx_runtime_1.jsx)(react_hook_form_1.FormProvider, { ...methods, children: (0, jsx_runtime_1.jsx)(react_1.Form, { onSubmit: methods.handleSubmit(handleSubmit), method: method, id: id, children: children }) }));
26
+ return ((0, jsx_runtime_1.jsx)(react_hook_form_1.FormProvider, { ...methods, children: (0, jsx_runtime_1.jsx)(react_1.Form, { onSubmit: methods.handleSubmit(handleSubmit), method: method, id: id, className: className, children: children }) }));
27
27
  };
28
28
  exports.ReactZodForm = ReactZodForm;
29
29
  exports.default = exports.ReactZodForm;
@@ -31,7 +31,7 @@ const SelectField = (props) => {
31
31
  const isReadOnly = disabled || readOnly;
32
32
  const bgColor = isReadOnly ? "bg-gray-200" : `bg-${color}-50`;
33
33
  const textColor = isReadOnly ? "text-gray-500" : `text-${color}-900`;
34
- const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-500`;
34
+ const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-200`;
35
35
  (0, react_1.useEffect)(() => {
36
36
  setFilteredOptions(options);
37
37
  }, [options]);
@@ -45,7 +45,9 @@ const SelectField = (props) => {
45
45
  if (defaultValue) {
46
46
  setValue(defaultValue);
47
47
  setInputValue(defaultValue.label);
48
- setFormValue(name || "", defaultValue.value);
48
+ if (setFormValue) {
49
+ setFormValue(name || "", defaultValue.value);
50
+ }
49
51
  }
50
52
  document.addEventListener("mousedown", handleClickOutside);
51
53
  return () => {
@@ -69,13 +71,17 @@ const SelectField = (props) => {
69
71
  : [...valueMulti, option];
70
72
  setValueMulti(newValue);
71
73
  onChangeMulti && onChangeMulti(newValue);
72
- setFormValue(name || "", newValue.map((v) => v.value));
74
+ if (setFormValue) {
75
+ setFormValue(name || "", newValue.map((v) => v.value));
76
+ }
73
77
  }
74
78
  else {
75
79
  setValue(option);
76
80
  setInputValue(option.label);
77
81
  onChange && onChange(option);
78
- setFormValue(name || "", option.value);
82
+ if (setFormValue) {
83
+ setFormValue(name || "", option.value);
84
+ }
79
85
  }
80
86
  setIsOpen(false);
81
87
  };
@@ -83,12 +89,16 @@ const SelectField = (props) => {
83
89
  if (isMulti) {
84
90
  setValueMulti([]);
85
91
  onChangeMulti && onChangeMulti([]);
86
- setFormValue(name || "", []);
92
+ if (setFormValue) {
93
+ setFormValue(name || "", []);
94
+ }
87
95
  }
88
96
  else {
89
97
  setValue(null);
90
98
  onChange && onChange(null);
91
- setFormValue(name || "", "");
99
+ if (setFormValue) {
100
+ setFormValue(name || "", "");
101
+ }
92
102
  }
93
103
  setInputValue("");
94
104
  };
@@ -21,9 +21,11 @@ const TextField = (props) => {
21
21
  const isReadOnly = disabled || readOnly;
22
22
  const bgColor = isReadOnly ? "bg-gray-200" : `bg-${color}-50`;
23
23
  const textColor = isReadOnly ? "text-gray-500" : `text-${color}-900`;
24
- const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-500`;
24
+ const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-200`;
25
25
  (0, react_1.useEffect)(() => {
26
- setOnFormValue(name ?? "-1", defaultValue);
26
+ if (setOnFormValue) {
27
+ setOnFormValue(name ?? "-1", defaultValue);
28
+ }
27
29
  setValue(defaultValue);
28
30
  }, [defaultValue]);
29
31
  const handleInputChange = (event) => {
@@ -17,7 +17,7 @@ const FormTimePicker = (props) => {
17
17
  const color = error ? "red" : "gray";
18
18
  const isReadOnly = disabled;
19
19
  const bgColor = isReadOnly ? "bg-gray-200" : `bg-${color}-50`;
20
- const textColor = isReadOnly ? "text-gray-500" : `text-${color}-500`;
20
+ const textColor = isReadOnly ? "text-gray-500" : `text-${color}-900`;
21
21
  const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-200`;
22
22
  (0, react_1.useEffect)(() => {
23
23
  setValue(defaultValue);
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const jsx_runtime_1 = require("react/jsx-runtime");
4
+ const react_1 = require("react");
5
+ const framer_motion_1 = require("framer-motion");
6
+ // SVG icon for the left-pointing chevron
7
+ const ChevronLeftIcon = () => ((0, jsx_runtime_1.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "h-6 w-6", children: (0, jsx_runtime_1.jsx)("path", { fillRule: "evenodd", d: "M7.72 12.53a.75.75 0 010-1.06l7.5-7.5a.75.75 0 111.06 1.06L9.31 12l6.97 6.97a.75.75 0 11-1.06 1.06l-7.5-7.5z", clipRule: "evenodd" }) }));
8
+ // SVG icon for the right-pointing chevron
9
+ const ChevronRightIcon = () => ((0, jsx_runtime_1.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "h-6 w-6", children: (0, jsx_runtime_1.jsx)("path", { fillRule: "evenodd", d: "M16.28 11.47a.75.75 0 010 1.06l-7.5 7.5a.75.75 0 01-1.06-1.06L14.69 12 7.72 5.03a.75.75 0 011.06-1.06l7.5 7.5z", clipRule: "evenodd" }) }));
10
+ // SVG icon for the filter (bars filter icon)
11
+ const FilterIcon = () => ((0, jsx_runtime_1.jsx)("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "h-6 w-6", children: (0, jsx_runtime_1.jsx)("path", { fillRule: "evenodd", d: "M3 6a1 1 0 011-1h16a1 1 0 110 2H4a1 1 0 01-1-1zm0 6a1 1 0 011-1h10a1 1 0 110 2H4a1 1 0 01-1-1zm0 6a1 1 0 011-1h4a1 1 0 110 2H4a1 1 0 01-1-1z", clipRule: "evenodd" }) }));
12
+ const SidePanel = ({ children, closeOnClickOutside = true, widthPercentage = 25, buttonIcon = "chevron", }) => {
13
+ const [isOpen, setIsOpen] = (0, react_1.useState)(false);
14
+ const panelRef = (0, react_1.useRef)(null);
15
+ const togglePanel = () => {
16
+ setIsOpen(!isOpen);
17
+ };
18
+ (0, react_1.useEffect)(() => {
19
+ const handleClickOutside = (event) => {
20
+ if (closeOnClickOutside &&
21
+ panelRef.current &&
22
+ !panelRef.current.contains(event.target)) {
23
+ setIsOpen(false);
24
+ }
25
+ };
26
+ document.addEventListener("mousedown", handleClickOutside);
27
+ return () => {
28
+ document.removeEventListener("mousedown", handleClickOutside);
29
+ };
30
+ }, [closeOnClickOutside]);
31
+ const renderIcon = () => {
32
+ if (typeof buttonIcon === "string") {
33
+ switch (buttonIcon) {
34
+ case "chevron":
35
+ return isOpen ? (0, jsx_runtime_1.jsx)(ChevronRightIcon, {}) : (0, jsx_runtime_1.jsx)(ChevronLeftIcon, {});
36
+ case "filter":
37
+ return (0, jsx_runtime_1.jsx)(FilterIcon, {});
38
+ default:
39
+ return (0, jsx_runtime_1.jsx)(ChevronLeftIcon, {});
40
+ }
41
+ }
42
+ else {
43
+ return buttonIcon;
44
+ }
45
+ };
46
+ return ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(framer_motion_1.AnimatePresence, { children: isOpen && ((0, jsx_runtime_1.jsxs)(framer_motion_1.motion.div, { ref: panelRef, initial: { x: "100%" }, animate: { x: 0 }, exit: { x: "100%" }, transition: { type: "spring", stiffness: 300, damping: 30 }, className: "fixed top-0 right-0 h-full bg-white shadow-lg z-[9999] overflow-y-auto", style: { width: `${widthPercentage}%` }, children: [(0, jsx_runtime_1.jsx)("button", { onClick: togglePanel, className: "absolute top-4 left-4 p-2 rounded-full bg-gray-200 hover:bg-gray-300 transition-colors", children: (0, jsx_runtime_1.jsx)(ChevronRightIcon, {}) }), (0, jsx_runtime_1.jsx)("div", { className: "p-6 mt-16", children: children })] })) }), !isOpen && ((0, jsx_runtime_1.jsx)("button", { onClick: togglePanel, className: "fixed top-1/2 right-0 transform -translate-y-1/2 bg-indigo-600 text-white p-3 rounded-l-lg shadow-md hover:bg-indigo-700 transition-colors z-[10000]", children: renderIcon() }))] }));
47
+ };
48
+ exports.default = SidePanel;
package/dist/cjs/index.js CHANGED
@@ -40,3 +40,4 @@ __exportStar(require("./WithTooltip/index.js"), exports);
40
40
  __exportStar(require("./Wizards/index.js"), exports);
41
41
  __exportStar(require("./Zendesk/index.js"), exports);
42
42
  __exportStar(require("./HOC/ValidateEmployeeAccess/index.js"), exports);
43
+ __exportStar(require("./SidePanel/index.js"), exports);
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useState } from "react";
3
3
  import { CalendarIconSVG, CloseSvgIcon, IdeaIconSVG } from "@zauru-sdk/icons";
4
4
  import { useFormContext } from "react-hook-form";
@@ -23,7 +23,7 @@ export const FormDatePicker = (props) => {
23
23
  setValue("");
24
24
  onChange && onChange("");
25
25
  };
26
- return (_jsxs(_Fragment, { children: [title && (_jsxs("label", { htmlFor: error ? `${name}-error` : `${name}-success`, className: `block text-sm font-medium ${textColor} ${className}`, children: [title, required && _jsx("span", { className: "text-red-500", children: "*" })] })), _jsxs("div", { className: "flex relative items-center", children: [_jsx("div", { className: "absolute left-0 flex items-center pl-3 pointer-events-none", children: _jsx(CalendarIconSVG, {}) }), _jsx("input", { id: id, tabIndex: tabIndex, type: "date", value: value ?? "", pattern: "\\d{4}-\\d{2}-\\d{2}", className: `${bgColor} ${borderColor} ${textColor} text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 p-2.5`, ...(register ?? {}), name: name, onChange: (e) => {
26
+ return (_jsxs("div", { children: [title && (_jsxs("label", { htmlFor: error ? `${name}-error` : `${name}-success`, className: `block text-sm font-medium ${textColor} ${className}`, children: [title, required && _jsx("span", { className: "text-red-500", children: "*" })] })), _jsxs("div", { className: "flex relative items-center", children: [_jsx("div", { className: "absolute left-0 flex items-center pl-3 pointer-events-none", children: _jsx(CalendarIconSVG, {}) }), _jsx("input", { id: id, tabIndex: tabIndex, type: "date", value: value ?? "", pattern: "\\d{4}-\\d{2}-\\d{2}", className: `${bgColor} ${borderColor} ${textColor} text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full pl-10 p-2.5`, ...(register ?? {}), name: name, onChange: (e) => {
27
27
  setValue(e.target.value);
28
28
  onChange && onChange(e.target.value);
29
29
  if (register) {
@@ -5,7 +5,7 @@ import { FormProvider, useForm, } from "react-hook-form";
5
5
  import { z } from "zod";
6
6
  const emptySchema = z.any();
7
7
  export const ReactZodForm = (props) => {
8
- const { children, method = "post", schema = emptySchema, onSubmit, id, } = props;
8
+ const { children, method = "post", schema = emptySchema, onSubmit, id, className, } = props;
9
9
  const submit = useSubmit();
10
10
  const methods = useForm({
11
11
  resolver: zodResolver(schema),
@@ -20,6 +20,6 @@ export const ReactZodForm = (props) => {
20
20
  submit(event?.target, { method });
21
21
  }
22
22
  };
23
- return (_jsx(FormProvider, { ...methods, children: _jsx(Form, { onSubmit: methods.handleSubmit(handleSubmit), method: method, id: id, children: children }) }));
23
+ return (_jsx(FormProvider, { ...methods, children: _jsx(Form, { onSubmit: methods.handleSubmit(handleSubmit), method: method, id: id, className: className, children: children }) }));
24
24
  };
25
25
  export default ReactZodForm;
@@ -28,7 +28,7 @@ export const SelectField = (props) => {
28
28
  const isReadOnly = disabled || readOnly;
29
29
  const bgColor = isReadOnly ? "bg-gray-200" : `bg-${color}-50`;
30
30
  const textColor = isReadOnly ? "text-gray-500" : `text-${color}-900`;
31
- const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-500`;
31
+ const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-200`;
32
32
  useEffect(() => {
33
33
  setFilteredOptions(options);
34
34
  }, [options]);
@@ -42,7 +42,9 @@ export const SelectField = (props) => {
42
42
  if (defaultValue) {
43
43
  setValue(defaultValue);
44
44
  setInputValue(defaultValue.label);
45
- setFormValue(name || "", defaultValue.value);
45
+ if (setFormValue) {
46
+ setFormValue(name || "", defaultValue.value);
47
+ }
46
48
  }
47
49
  document.addEventListener("mousedown", handleClickOutside);
48
50
  return () => {
@@ -66,13 +68,17 @@ export const SelectField = (props) => {
66
68
  : [...valueMulti, option];
67
69
  setValueMulti(newValue);
68
70
  onChangeMulti && onChangeMulti(newValue);
69
- setFormValue(name || "", newValue.map((v) => v.value));
71
+ if (setFormValue) {
72
+ setFormValue(name || "", newValue.map((v) => v.value));
73
+ }
70
74
  }
71
75
  else {
72
76
  setValue(option);
73
77
  setInputValue(option.label);
74
78
  onChange && onChange(option);
75
- setFormValue(name || "", option.value);
79
+ if (setFormValue) {
80
+ setFormValue(name || "", option.value);
81
+ }
76
82
  }
77
83
  setIsOpen(false);
78
84
  };
@@ -80,12 +86,16 @@ export const SelectField = (props) => {
80
86
  if (isMulti) {
81
87
  setValueMulti([]);
82
88
  onChangeMulti && onChangeMulti([]);
83
- setFormValue(name || "", []);
89
+ if (setFormValue) {
90
+ setFormValue(name || "", []);
91
+ }
84
92
  }
85
93
  else {
86
94
  setValue(null);
87
95
  onChange && onChange(null);
88
- setFormValue(name || "", "");
96
+ if (setFormValue) {
97
+ setFormValue(name || "", "");
98
+ }
89
99
  }
90
100
  setInputValue("");
91
101
  };
@@ -18,9 +18,11 @@ export const TextField = (props) => {
18
18
  const isReadOnly = disabled || readOnly;
19
19
  const bgColor = isReadOnly ? "bg-gray-200" : `bg-${color}-50`;
20
20
  const textColor = isReadOnly ? "text-gray-500" : `text-${color}-900`;
21
- const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-500`;
21
+ const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-200`;
22
22
  useEffect(() => {
23
- setOnFormValue(name ?? "-1", defaultValue);
23
+ if (setOnFormValue) {
24
+ setOnFormValue(name ?? "-1", defaultValue);
25
+ }
24
26
  setValue(defaultValue);
25
27
  }, [defaultValue]);
26
28
  const handleInputChange = (event) => {
@@ -14,7 +14,7 @@ export const FormTimePicker = (props) => {
14
14
  const color = error ? "red" : "gray";
15
15
  const isReadOnly = disabled;
16
16
  const bgColor = isReadOnly ? "bg-gray-200" : `bg-${color}-50`;
17
- const textColor = isReadOnly ? "text-gray-500" : `text-${color}-500`;
17
+ const textColor = isReadOnly ? "text-gray-500" : `text-${color}-900`;
18
18
  const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-200`;
19
19
  useEffect(() => {
20
20
  setValue(defaultValue);
@@ -0,0 +1,46 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef } from "react";
3
+ import { motion, AnimatePresence } from "framer-motion";
4
+ // SVG icon for the left-pointing chevron
5
+ const ChevronLeftIcon = () => (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "h-6 w-6", children: _jsx("path", { fillRule: "evenodd", d: "M7.72 12.53a.75.75 0 010-1.06l7.5-7.5a.75.75 0 111.06 1.06L9.31 12l6.97 6.97a.75.75 0 11-1.06 1.06l-7.5-7.5z", clipRule: "evenodd" }) }));
6
+ // SVG icon for the right-pointing chevron
7
+ const ChevronRightIcon = () => (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "h-6 w-6", children: _jsx("path", { fillRule: "evenodd", d: "M16.28 11.47a.75.75 0 010 1.06l-7.5 7.5a.75.75 0 01-1.06-1.06L14.69 12 7.72 5.03a.75.75 0 011.06-1.06l7.5 7.5z", clipRule: "evenodd" }) }));
8
+ // SVG icon for the filter (bars filter icon)
9
+ const FilterIcon = () => (_jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 24 24", fill: "currentColor", className: "h-6 w-6", children: _jsx("path", { fillRule: "evenodd", d: "M3 6a1 1 0 011-1h16a1 1 0 110 2H4a1 1 0 01-1-1zm0 6a1 1 0 011-1h10a1 1 0 110 2H4a1 1 0 01-1-1zm0 6a1 1 0 011-1h4a1 1 0 110 2H4a1 1 0 01-1-1z", clipRule: "evenodd" }) }));
10
+ const SidePanel = ({ children, closeOnClickOutside = true, widthPercentage = 25, buttonIcon = "chevron", }) => {
11
+ const [isOpen, setIsOpen] = useState(false);
12
+ const panelRef = useRef(null);
13
+ const togglePanel = () => {
14
+ setIsOpen(!isOpen);
15
+ };
16
+ useEffect(() => {
17
+ const handleClickOutside = (event) => {
18
+ if (closeOnClickOutside &&
19
+ panelRef.current &&
20
+ !panelRef.current.contains(event.target)) {
21
+ setIsOpen(false);
22
+ }
23
+ };
24
+ document.addEventListener("mousedown", handleClickOutside);
25
+ return () => {
26
+ document.removeEventListener("mousedown", handleClickOutside);
27
+ };
28
+ }, [closeOnClickOutside]);
29
+ const renderIcon = () => {
30
+ if (typeof buttonIcon === "string") {
31
+ switch (buttonIcon) {
32
+ case "chevron":
33
+ return isOpen ? _jsx(ChevronRightIcon, {}) : _jsx(ChevronLeftIcon, {});
34
+ case "filter":
35
+ return _jsx(FilterIcon, {});
36
+ default:
37
+ return _jsx(ChevronLeftIcon, {});
38
+ }
39
+ }
40
+ else {
41
+ return buttonIcon;
42
+ }
43
+ };
44
+ return (_jsxs(_Fragment, { children: [_jsx(AnimatePresence, { children: isOpen && (_jsxs(motion.div, { ref: panelRef, initial: { x: "100%" }, animate: { x: 0 }, exit: { x: "100%" }, transition: { type: "spring", stiffness: 300, damping: 30 }, className: "fixed top-0 right-0 h-full bg-white shadow-lg z-[9999] overflow-y-auto", style: { width: `${widthPercentage}%` }, children: [_jsx("button", { onClick: togglePanel, className: "absolute top-4 left-4 p-2 rounded-full bg-gray-200 hover:bg-gray-300 transition-colors", children: _jsx(ChevronRightIcon, {}) }), _jsx("div", { className: "p-6 mt-16", children: children })] })) }), !isOpen && (_jsx("button", { onClick: togglePanel, className: "fixed top-1/2 right-0 transform -translate-y-1/2 bg-indigo-600 text-white p-3 rounded-l-lg shadow-md hover:bg-indigo-700 transition-colors z-[10000]", children: renderIcon() }))] }));
45
+ };
46
+ export default SidePanel;
package/dist/esm/index.js CHANGED
@@ -24,3 +24,4 @@ export * from "./WithTooltip/index.js";
24
24
  export * from "./Wizards/index.js";
25
25
  export * from "./Zendesk/index.js";
26
26
  export * from "./HOC/ValidateEmployeeAccess/index.js";
27
+ export * from "./SidePanel/index.js";
package/dist/index.d.ts CHANGED
@@ -24,3 +24,4 @@ export * from "./WithTooltip/index.js";
24
24
  export * from "./Wizards/index.js";
25
25
  export * from "./Zendesk/index.js";
26
26
  export * from "./HOC/ValidateEmployeeAccess/index.js";
27
+ export * from "./SidePanel/index.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zauru-sdk/components",
3
- "version": "1.0.113",
3
+ "version": "1.0.115",
4
4
  "description": "Componentes reutilizables en las WebApps de Zauru.",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",
@@ -38,8 +38,8 @@
38
38
  "@zauru-sdk/hooks": "^1.0.112",
39
39
  "@zauru-sdk/icons": "^1.0.60",
40
40
  "@zauru-sdk/types": "^1.0.109",
41
- "@zauru-sdk/utils": "^1.0.113",
42
- "framer-motion": "^11.0.8",
41
+ "@zauru-sdk/utils": "^1.0.115",
42
+ "framer-motion": "^11.7.0",
43
43
  "jsonwebtoken": "^9.0.2",
44
44
  "react": "^18.2.0",
45
45
  "react-data-table-component": "^7.6.2",
@@ -50,5 +50,5 @@
50
50
  "styled-components": "^5.3.5",
51
51
  "zod": "^3.23.8"
52
52
  },
53
- "gitHead": "32d6e9f7c23ac5b58be802d23f007f86b4aea904"
53
+ "gitHead": "9a49125d2c7974621d59f96389b9e08e3fa68dc9"
54
54
  }
@@ -61,7 +61,7 @@ export const FormDatePicker = (props: Props) => {
61
61
  };
62
62
 
63
63
  return (
64
- <>
64
+ <div>
65
65
  {title && (
66
66
  <label
67
67
  htmlFor={error ? `${name}-error` : `${name}-success`}
@@ -130,6 +130,6 @@ export const FormDatePicker = (props: Props) => {
130
130
  {hint}
131
131
  </p>
132
132
  )}
133
- </>
133
+ </div>
134
134
  );
135
135
  };
@@ -15,6 +15,7 @@ type Props = {
15
15
  onSubmit?: SubmitHandler<FieldValues>;
16
16
  id?: string;
17
17
  method?: "post" | "put" | "delete" | "patch";
18
+ className?: string;
18
19
  };
19
20
 
20
21
  const emptySchema = z.any();
@@ -26,6 +27,7 @@ export const ReactZodForm = (props: Props) => {
26
27
  schema = emptySchema,
27
28
  onSubmit,
28
29
  id,
30
+ className,
29
31
  } = props;
30
32
 
31
33
  const submit = useSubmit();
@@ -49,6 +51,7 @@ export const ReactZodForm = (props: Props) => {
49
51
  onSubmit={methods.handleSubmit(handleSubmit)}
50
52
  method={method}
51
53
  id={id}
54
+ className={className}
52
55
  >
53
56
  {children}
54
57
  </Form>
@@ -79,7 +79,7 @@ export const SelectField = (props: Props) => {
79
79
  const isReadOnly = disabled || readOnly;
80
80
  const bgColor = isReadOnly ? "bg-gray-200" : `bg-${color}-50`;
81
81
  const textColor = isReadOnly ? "text-gray-500" : `text-${color}-900`;
82
- const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-500`;
82
+ const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-200`;
83
83
 
84
84
  useEffect(() => {
85
85
  setFilteredOptions(options);
@@ -98,7 +98,9 @@ export const SelectField = (props: Props) => {
98
98
  if (defaultValue) {
99
99
  setValue(defaultValue);
100
100
  setInputValue(defaultValue.label);
101
- setFormValue(name || "", defaultValue.value);
101
+ if (setFormValue) {
102
+ setFormValue(name || "", defaultValue.value);
103
+ }
102
104
  }
103
105
 
104
106
  document.addEventListener("mousedown", handleClickOutside);
@@ -129,15 +131,19 @@ export const SelectField = (props: Props) => {
129
131
  : [...valueMulti, option];
130
132
  setValueMulti(newValue);
131
133
  onChangeMulti && onChangeMulti(newValue);
132
- setFormValue(
133
- name || "",
134
- newValue.map((v) => v.value)
135
- );
134
+ if (setFormValue) {
135
+ setFormValue(
136
+ name || "",
137
+ newValue.map((v) => v.value)
138
+ );
139
+ }
136
140
  } else {
137
141
  setValue(option);
138
142
  setInputValue(option.label);
139
143
  onChange && onChange(option);
140
- setFormValue(name || "", option.value);
144
+ if (setFormValue) {
145
+ setFormValue(name || "", option.value);
146
+ }
141
147
  }
142
148
  setIsOpen(false);
143
149
  };
@@ -146,11 +152,15 @@ export const SelectField = (props: Props) => {
146
152
  if (isMulti) {
147
153
  setValueMulti([]);
148
154
  onChangeMulti && onChangeMulti([]);
149
- setFormValue(name || "", []);
155
+ if (setFormValue) {
156
+ setFormValue(name || "", []);
157
+ }
150
158
  } else {
151
159
  setValue(null);
152
160
  onChange && onChange(null);
153
- setFormValue(name || "", "");
161
+ if (setFormValue) {
162
+ setFormValue(name || "", "");
163
+ }
154
164
  }
155
165
  setInputValue("");
156
166
  };
@@ -68,10 +68,12 @@ export const TextField = (props: Props) => {
68
68
  const isReadOnly = disabled || readOnly;
69
69
  const bgColor = isReadOnly ? "bg-gray-200" : `bg-${color}-50`;
70
70
  const textColor = isReadOnly ? "text-gray-500" : `text-${color}-900`;
71
- const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-500`;
71
+ const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-200`;
72
72
 
73
73
  useEffect(() => {
74
- setOnFormValue(name ?? "-1", defaultValue);
74
+ if (setOnFormValue) {
75
+ setOnFormValue(name ?? "-1", defaultValue);
76
+ }
75
77
  setValue(defaultValue);
76
78
  }, [defaultValue]);
77
79
 
@@ -45,7 +45,7 @@ export const FormTimePicker = (props: Props) => {
45
45
 
46
46
  const isReadOnly = disabled;
47
47
  const bgColor = isReadOnly ? "bg-gray-200" : `bg-${color}-50`;
48
- const textColor = isReadOnly ? "text-gray-500" : `text-${color}-500`;
48
+ const textColor = isReadOnly ? "text-gray-500" : `text-${color}-900`;
49
49
  const borderColor = isReadOnly ? "border-gray-300" : `border-${color}-200`;
50
50
 
51
51
  useEffect(() => {
@@ -0,0 +1,153 @@
1
+ import React, { useState, ReactNode, useEffect, useRef } from "react";
2
+ import { motion, AnimatePresence } from "framer-motion";
3
+
4
+ /**
5
+ * SidePanel Component
6
+ *
7
+ * This component creates a collapsible side panel that can be toggled open and closed.
8
+ *
9
+ * @param {ReactNode} children - The content to be displayed inside the side panel.
10
+ * @param {boolean} [closeOnClickOutside=true] - If true, the panel will close when clicking outside of it.
11
+ * @param {number} [widthPercentage=25] - The width of the panel as a percentage of the viewport width.
12
+ * @param {string} [buttonIcon="chevron"] - The icon to use for the toggle button. Can be "chevron", "filter", or a custom ReactNode.
13
+ *
14
+ * @returns A toggleable side panel component.
15
+ */
16
+
17
+ interface SidePanelProps {
18
+ children: ReactNode;
19
+ closeOnClickOutside?: boolean;
20
+ widthPercentage?: number;
21
+ buttonIcon?: "chevron" | "filter" | ReactNode;
22
+ }
23
+
24
+ // SVG icon for the left-pointing chevron
25
+ const ChevronLeftIcon = () => (
26
+ <svg
27
+ xmlns="http://www.w3.org/2000/svg"
28
+ viewBox="0 0 24 24"
29
+ fill="currentColor"
30
+ className="h-6 w-6"
31
+ >
32
+ <path
33
+ fillRule="evenodd"
34
+ d="M7.72 12.53a.75.75 0 010-1.06l7.5-7.5a.75.75 0 111.06 1.06L9.31 12l6.97 6.97a.75.75 0 11-1.06 1.06l-7.5-7.5z"
35
+ clipRule="evenodd"
36
+ />
37
+ </svg>
38
+ );
39
+
40
+ // SVG icon for the right-pointing chevron
41
+ const ChevronRightIcon = () => (
42
+ <svg
43
+ xmlns="http://www.w3.org/2000/svg"
44
+ viewBox="0 0 24 24"
45
+ fill="currentColor"
46
+ className="h-6 w-6"
47
+ >
48
+ <path
49
+ fillRule="evenodd"
50
+ d="M16.28 11.47a.75.75 0 010 1.06l-7.5 7.5a.75.75 0 01-1.06-1.06L14.69 12 7.72 5.03a.75.75 0 011.06-1.06l7.5 7.5z"
51
+ clipRule="evenodd"
52
+ />
53
+ </svg>
54
+ );
55
+
56
+ // SVG icon for the filter (bars filter icon)
57
+ const FilterIcon = () => (
58
+ <svg
59
+ xmlns="http://www.w3.org/2000/svg"
60
+ viewBox="0 0 24 24"
61
+ fill="currentColor"
62
+ className="h-6 w-6"
63
+ >
64
+ <path
65
+ fillRule="evenodd"
66
+ d="M3 6a1 1 0 011-1h16a1 1 0 110 2H4a1 1 0 01-1-1zm0 6a1 1 0 011-1h10a1 1 0 110 2H4a1 1 0 01-1-1zm0 6a1 1 0 011-1h4a1 1 0 110 2H4a1 1 0 01-1-1z"
67
+ clipRule="evenodd"
68
+ />
69
+ </svg>
70
+ );
71
+
72
+ const SidePanel: React.FC<SidePanelProps> = ({
73
+ children,
74
+ closeOnClickOutside = true,
75
+ widthPercentage = 25,
76
+ buttonIcon = "chevron",
77
+ }) => {
78
+ const [isOpen, setIsOpen] = useState(false);
79
+ const panelRef = useRef<HTMLDivElement>(null);
80
+
81
+ const togglePanel = () => {
82
+ setIsOpen(!isOpen);
83
+ };
84
+
85
+ useEffect(() => {
86
+ const handleClickOutside = (event: MouseEvent) => {
87
+ if (
88
+ closeOnClickOutside &&
89
+ panelRef.current &&
90
+ !panelRef.current.contains(event.target as Node)
91
+ ) {
92
+ setIsOpen(false);
93
+ }
94
+ };
95
+
96
+ document.addEventListener("mousedown", handleClickOutside);
97
+ return () => {
98
+ document.removeEventListener("mousedown", handleClickOutside);
99
+ };
100
+ }, [closeOnClickOutside]);
101
+
102
+ const renderIcon = () => {
103
+ if (typeof buttonIcon === "string") {
104
+ switch (buttonIcon) {
105
+ case "chevron":
106
+ return isOpen ? <ChevronRightIcon /> : <ChevronLeftIcon />;
107
+ case "filter":
108
+ return <FilterIcon />;
109
+ default:
110
+ return <ChevronLeftIcon />;
111
+ }
112
+ } else {
113
+ return buttonIcon;
114
+ }
115
+ };
116
+
117
+ return (
118
+ <>
119
+ <AnimatePresence>
120
+ {isOpen && (
121
+ <motion.div
122
+ ref={panelRef}
123
+ initial={{ x: "100%" }}
124
+ animate={{ x: 0 }}
125
+ exit={{ x: "100%" }}
126
+ transition={{ type: "spring", stiffness: 300, damping: 30 }}
127
+ className="fixed top-0 right-0 h-full bg-white shadow-lg z-[9999] overflow-y-auto"
128
+ style={{ width: `${widthPercentage}%` }}
129
+ >
130
+ <button
131
+ onClick={togglePanel}
132
+ className="absolute top-4 left-4 p-2 rounded-full bg-gray-200 hover:bg-gray-300 transition-colors"
133
+ >
134
+ <ChevronRightIcon />
135
+ </button>
136
+ <div className="p-6 mt-16">{children}</div>
137
+ </motion.div>
138
+ )}
139
+ </AnimatePresence>
140
+
141
+ {!isOpen && (
142
+ <button
143
+ onClick={togglePanel}
144
+ className="fixed top-1/2 right-0 transform -translate-y-1/2 bg-indigo-600 text-white p-3 rounded-l-lg shadow-md hover:bg-indigo-700 transition-colors z-[10000]"
145
+ >
146
+ {renderIcon()}
147
+ </button>
148
+ )}
149
+ </>
150
+ );
151
+ };
152
+
153
+ export default SidePanel;
package/src/index.ts CHANGED
@@ -24,3 +24,4 @@ export * from "./WithTooltip/index.js";
24
24
  export * from "./Wizards/index.js";
25
25
  export * from "./Zendesk/index.js";
26
26
  export * from "./HOC/ValidateEmployeeAccess/index.js";
27
+ export * from "./SidePanel/index.js";