@robosophy/ui-react 1.0.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.
@@ -0,0 +1,405 @@
1
+ import {
2
+ RsIcon_default
3
+ } from "./chunk-PEJOAJYL.js";
4
+
5
+ // src/components/RsSidebar/RsSidebar.tsx
6
+ import { forwardRef, useCallback, useEffect, useMemo, useRef, useState } from "react";
7
+ import { Menu } from "primereact/menu";
8
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
9
+ var variantIconMap = {
10
+ pc: "article",
11
+ alta: "account_circle",
12
+ cc: "account_balance_wallet",
13
+ pog: "account_balance_wallet",
14
+ cyo: "nest_eco_leaf"
15
+ };
16
+ function isVisible(item) {
17
+ return item.visible !== false;
18
+ }
19
+ function hasVisibleChildren(item) {
20
+ return (item.children ?? []).some(isVisible);
21
+ }
22
+ var RsSidebar = forwardRef(
23
+ ({
24
+ items,
25
+ footerItems = [],
26
+ modules = [],
27
+ navigateOnModuleSelect = false,
28
+ title = "",
29
+ icon,
30
+ expanded = false,
31
+ expandOnHover = true,
32
+ mobileOpen = false,
33
+ ariaLabel = "Navegacion principal",
34
+ variant = "cyo",
35
+ onItemClick,
36
+ onCloseMobile,
37
+ onExpandedChange,
38
+ onModuleSelect,
39
+ header,
40
+ headerAction,
41
+ renderItem,
42
+ footer
43
+ }, ref) => {
44
+ const [isHovered, setIsHovered] = useState(false);
45
+ const [activeParent, setActiveParent] = useState(null);
46
+ const [isModuleMenuOpen, setIsModuleMenuOpen] = useState(false);
47
+ const rootRef = useRef(null);
48
+ const isFirstRenderRef = useRef(true);
49
+ const onExpandedChangeRef = useRef(onExpandedChange);
50
+ onExpandedChangeRef.current = onExpandedChange;
51
+ const setRefs = useCallback(
52
+ (node) => {
53
+ rootRef.current = node;
54
+ if (typeof ref === "function") {
55
+ ref(node);
56
+ } else if (ref) {
57
+ ref.current = node;
58
+ }
59
+ },
60
+ [ref]
61
+ );
62
+ const visibleItems = useMemo(() => items.filter(isVisible), [items]);
63
+ const visibleFooterItems = useMemo(() => footerItems.filter(isVisible), [footerItems]);
64
+ const activeModule = useMemo(
65
+ () => modules.find((module) => module.active) ?? modules.find((module) => module.text === title) ?? null,
66
+ [modules, title]
67
+ );
68
+ const displayedHeaderTitle = useMemo(
69
+ () => activeModule?.text || title,
70
+ [activeModule, title]
71
+ );
72
+ const selectModule = useCallback(
73
+ (module) => {
74
+ if (module.disabled) {
75
+ return;
76
+ }
77
+ setIsModuleMenuOpen(false);
78
+ onModuleSelect?.(module);
79
+ if (navigateOnModuleSelect && typeof window !== "undefined" && module.link) {
80
+ window.location.href = module.link;
81
+ }
82
+ },
83
+ [navigateOnModuleSelect, onModuleSelect]
84
+ );
85
+ const moduleMenuItems = useMemo(
86
+ () => modules.map((module) => {
87
+ const isCurrent = module.active || module.text === displayedHeaderTitle;
88
+ return {
89
+ className: [
90
+ "rs-sidebar__module-option",
91
+ isCurrent ? "rs-sidebar__module-option--active" : "",
92
+ module.disabled || isCurrent ? "rs-sidebar__module-option--disabled" : ""
93
+ ].filter(Boolean).join(" "),
94
+ command: () => selectModule(module),
95
+ disabled: module.disabled || isCurrent,
96
+ label: module.text
97
+ };
98
+ }),
99
+ [modules, displayedHeaderTitle, selectModule]
100
+ );
101
+ const displayedItems = useMemo(
102
+ () => activeParent ? (activeParent.children ?? []).filter(isVisible) : visibleItems,
103
+ [activeParent, visibleItems]
104
+ );
105
+ const isExpanded = useMemo(
106
+ () => expanded || mobileOpen || isHovered,
107
+ [expanded, mobileOpen, isHovered]
108
+ );
109
+ const hasHeaderAction = mobileOpen;
110
+ const hasModuleSelector = useMemo(() => modules.length > 0, [modules]);
111
+ const resolvedIcon = useMemo(() => icon ?? variantIconMap[variant], [icon, variant]);
112
+ const rootClass = useMemo(() => {
113
+ const classes = ["rs-sidebar", `rs-sidebar--${variant}`];
114
+ if (isExpanded) {
115
+ classes.push("rs-sidebar--expanded");
116
+ }
117
+ if (mobileOpen) {
118
+ classes.push("rs-sidebar--mobile-open");
119
+ }
120
+ if (activeParent) {
121
+ classes.push("rs-sidebar--submenu");
122
+ }
123
+ return classes.join(" ");
124
+ }, [variant, isExpanded, mobileOpen, activeParent]);
125
+ useEffect(() => {
126
+ if (!mobileOpen) {
127
+ setActiveParent(null);
128
+ }
129
+ setIsModuleMenuOpen(false);
130
+ }, [mobileOpen]);
131
+ useEffect(() => {
132
+ if (activeParent && !items.some((i) => i.id === activeParent.id)) {
133
+ setActiveParent(null);
134
+ }
135
+ }, [items, activeParent]);
136
+ useEffect(() => {
137
+ if (isFirstRenderRef.current) {
138
+ isFirstRenderRef.current = false;
139
+ return;
140
+ }
141
+ if (!isExpanded) {
142
+ setActiveParent(null);
143
+ }
144
+ onExpandedChangeRef.current?.(isExpanded);
145
+ }, [isExpanded]);
146
+ useEffect(() => {
147
+ setIsModuleMenuOpen(false);
148
+ }, [activeParent]);
149
+ useEffect(() => {
150
+ const handleDocumentPointerDown = (event) => {
151
+ if (!isModuleMenuOpen || !rootRef.current) {
152
+ return;
153
+ }
154
+ if (!rootRef.current.contains(event.target)) {
155
+ setIsModuleMenuOpen(false);
156
+ }
157
+ };
158
+ document.addEventListener("pointerdown", handleDocumentPointerDown);
159
+ return () => {
160
+ document.removeEventListener("pointerdown", handleDocumentPointerDown);
161
+ };
162
+ }, [isModuleMenuOpen]);
163
+ const setHovered = useCallback(
164
+ (value) => {
165
+ if (!expandOnHover || mobileOpen) {
166
+ return;
167
+ }
168
+ setIsHovered(value);
169
+ if (!value) {
170
+ setIsModuleMenuOpen(false);
171
+ }
172
+ },
173
+ [expandOnHover, mobileOpen]
174
+ );
175
+ const handleItemClick = useCallback(
176
+ (item) => {
177
+ if (item.disabled) {
178
+ return;
179
+ }
180
+ if (hasVisibleChildren(item)) {
181
+ setActiveParent(item);
182
+ return;
183
+ }
184
+ onItemClick?.(item);
185
+ },
186
+ [onItemClick]
187
+ );
188
+ const goBack = useCallback(() => {
189
+ setActiveParent(null);
190
+ }, []);
191
+ const closeMobile = useCallback(() => {
192
+ setActiveParent(null);
193
+ setIsModuleMenuOpen(false);
194
+ onCloseMobile?.();
195
+ }, [onCloseMobile]);
196
+ const toggleModuleMenu = useCallback(() => {
197
+ if (!hasModuleSelector || !isExpanded) {
198
+ return;
199
+ }
200
+ setIsModuleMenuOpen((prev) => !prev);
201
+ }, [hasModuleSelector, isExpanded]);
202
+ const renderHeaderContent = () => {
203
+ if (typeof header === "function") {
204
+ const slotProps = {
205
+ expanded: isExpanded,
206
+ activeParent,
207
+ closeMobile,
208
+ goBack
209
+ };
210
+ return header(slotProps);
211
+ }
212
+ if (header) {
213
+ return header;
214
+ }
215
+ return /* @__PURE__ */ jsxs("header", { className: "rs-sidebar__header", children: [
216
+ /* @__PURE__ */ jsx(
217
+ "div",
218
+ {
219
+ className: [
220
+ "rs-sidebar__brand",
221
+ hasModuleSelector ? "rs-sidebar__brand--trigger" : ""
222
+ ].filter(Boolean).join(" "),
223
+ children: hasModuleSelector ? /* @__PURE__ */ jsxs(
224
+ "button",
225
+ {
226
+ className: "rs-sidebar__module-trigger",
227
+ type: "button",
228
+ "aria-expanded": isModuleMenuOpen,
229
+ "aria-haspopup": "menu",
230
+ onClick: toggleModuleMenu,
231
+ children: [
232
+ /* @__PURE__ */ jsx(RsIcon_default, { name: resolvedIcon }),
233
+ /* @__PURE__ */ jsx("span", { className: "rs-sidebar__title", children: displayedHeaderTitle }),
234
+ /* @__PURE__ */ jsx(
235
+ RsIcon_default,
236
+ {
237
+ className: "rs-sidebar__module-chevron",
238
+ name: isModuleMenuOpen ? "arrow_drop_up" : "arrow_drop_down",
239
+ color: "secondary",
240
+ size: "sm"
241
+ }
242
+ )
243
+ ]
244
+ }
245
+ ) : /* @__PURE__ */ jsxs(Fragment, { children: [
246
+ /* @__PURE__ */ jsx(RsIcon_default, { name: resolvedIcon }),
247
+ /* @__PURE__ */ jsx("span", { className: "rs-sidebar__title", children: displayedHeaderTitle })
248
+ ] })
249
+ }
250
+ ),
251
+ renderHeaderAction()
252
+ ] });
253
+ };
254
+ const renderHeaderAction = () => {
255
+ if (typeof headerAction === "function") {
256
+ const slotProps = {
257
+ activeParent,
258
+ closeMobile,
259
+ goBack
260
+ };
261
+ return headerAction(slotProps);
262
+ }
263
+ if (headerAction) {
264
+ return headerAction;
265
+ }
266
+ if (hasHeaderAction) {
267
+ return /* @__PURE__ */ jsx(
268
+ "button",
269
+ {
270
+ className: "rs-sidebar__header-button",
271
+ type: "button",
272
+ "aria-label": "Cerrar menu",
273
+ onClick: closeMobile,
274
+ children: /* @__PURE__ */ jsx(RsIcon_default, { name: "close" })
275
+ }
276
+ );
277
+ }
278
+ return null;
279
+ };
280
+ const renderModuleMenu = () => {
281
+ if (!hasModuleSelector || !isModuleMenuOpen) {
282
+ return null;
283
+ }
284
+ return /* @__PURE__ */ jsx(
285
+ Menu,
286
+ {
287
+ className: "rs-sidebar__module-menu",
288
+ model: moduleMenuItems,
289
+ pt: {
290
+ root: { role: "menu" }
291
+ }
292
+ }
293
+ );
294
+ };
295
+ const renderFooterContent = () => {
296
+ if (typeof footer === "function") {
297
+ const slotProps = {
298
+ items: visibleFooterItems,
299
+ expanded: isExpanded
300
+ };
301
+ return footer(slotProps);
302
+ }
303
+ if (footer) {
304
+ return footer;
305
+ }
306
+ if (visibleFooterItems.length > 0) {
307
+ return /* @__PURE__ */ jsx("nav", { className: "rs-sidebar__footer", "aria-label": "Acciones secundarias", children: /* @__PURE__ */ jsx("ul", { className: "rs-sidebar__list", children: visibleFooterItems.map((item) => /* @__PURE__ */ jsx("li", { className: "rs-sidebar__list-item", children: /* @__PURE__ */ jsxs(
308
+ "button",
309
+ {
310
+ className: [
311
+ "rs-sidebar__item",
312
+ "rs-sidebar__item--footer",
313
+ item.active ? "rs-sidebar__item--active" : "",
314
+ item.disabled ? "rs-sidebar__item--disabled" : ""
315
+ ].filter(Boolean).join(" "),
316
+ type: "button",
317
+ disabled: item.disabled,
318
+ onClick: () => handleItemClick(item),
319
+ children: [
320
+ item.icon && /* @__PURE__ */ jsx(RsIcon_default, { className: "rs-sidebar__item-icon", name: item.icon }),
321
+ /* @__PURE__ */ jsx("span", { className: "rs-sidebar__item-label", children: item.label })
322
+ ]
323
+ }
324
+ ) }, item.id)) }) });
325
+ }
326
+ return null;
327
+ };
328
+ return /* @__PURE__ */ jsxs(
329
+ "aside",
330
+ {
331
+ ref: setRefs,
332
+ "aria-label": ariaLabel,
333
+ className: rootClass,
334
+ onMouseEnter: () => setHovered(true),
335
+ onMouseLeave: () => setHovered(false),
336
+ children: [
337
+ renderHeaderContent(),
338
+ renderModuleMenu(),
339
+ /* @__PURE__ */ jsxs("div", { className: "rs-sidebar__content", children: [
340
+ /* @__PURE__ */ jsx("nav", { className: "rs-sidebar__nav", "aria-label": "Secciones", children: /* @__PURE__ */ jsxs("ul", { className: "rs-sidebar__list", children: [
341
+ activeParent && /* @__PURE__ */ jsx("li", { className: "rs-sidebar__list-item", children: /* @__PURE__ */ jsxs(
342
+ "button",
343
+ {
344
+ className: "rs-sidebar__item rs-sidebar__item--back",
345
+ type: "button",
346
+ onClick: goBack,
347
+ children: [
348
+ /* @__PURE__ */ jsx(
349
+ RsIcon_default,
350
+ {
351
+ className: "rs-sidebar__item-icon",
352
+ name: "arrow_left",
353
+ fill: true
354
+ }
355
+ ),
356
+ /* @__PURE__ */ jsx("span", { className: "rs-sidebar__item-label", children: activeParent.label })
357
+ ]
358
+ }
359
+ ) }),
360
+ displayedItems.map((item) => /* @__PURE__ */ jsx("li", { className: "rs-sidebar__list-item", children: /* @__PURE__ */ jsx(
361
+ "button",
362
+ {
363
+ className: [
364
+ "rs-sidebar__item",
365
+ item.active ? "rs-sidebar__item--active" : "",
366
+ item.disabled ? "rs-sidebar__item--disabled" : "",
367
+ hasVisibleChildren(item) ? "rs-sidebar__item--parent" : ""
368
+ ].filter(Boolean).join(" "),
369
+ type: "button",
370
+ disabled: item.disabled,
371
+ onClick: () => handleItemClick(item),
372
+ children: renderItem ? renderItem(item, isExpanded) : /* @__PURE__ */ jsxs(Fragment, { children: [
373
+ item.icon && /* @__PURE__ */ jsx(
374
+ RsIcon_default,
375
+ {
376
+ className: "rs-sidebar__item-icon",
377
+ name: item.icon
378
+ }
379
+ ),
380
+ /* @__PURE__ */ jsx("span", { className: "rs-sidebar__item-label", children: item.label }),
381
+ hasVisibleChildren(item) && /* @__PURE__ */ jsx(
382
+ RsIcon_default,
383
+ {
384
+ className: "rs-sidebar__item-chevron",
385
+ name: "chevron_right",
386
+ size: "sm"
387
+ }
388
+ )
389
+ ] })
390
+ }
391
+ ) }, item.id))
392
+ ] }) }),
393
+ renderFooterContent()
394
+ ] })
395
+ ]
396
+ }
397
+ );
398
+ }
399
+ );
400
+ RsSidebar.displayName = "RsSidebar";
401
+ var RsSidebar_default = RsSidebar;
402
+
403
+ export {
404
+ RsSidebar_default
405
+ };
@@ -0,0 +1,12 @@
1
+ // src/components/RsIcon/gradeUtils.ts
2
+ var GRADE_VARIANTS = [0, -25, 200];
3
+ function clampGrade(grade) {
4
+ return GRADE_VARIANTS.reduce(
5
+ (nearest, g) => Math.abs(g - grade) < Math.abs(nearest - grade) ? g : nearest
6
+ );
7
+ }
8
+
9
+ export {
10
+ GRADE_VARIANTS,
11
+ clampGrade
12
+ };
@@ -0,0 +1,115 @@
1
+ import {
2
+ clampGrade
3
+ } from "./chunk-D4ZW5VUU.js";
4
+
5
+ // src/components/RsIcon/RsIcon.tsx
6
+ import { forwardRef, useMemo } from "react";
7
+ import { jsx, jsxs } from "react/jsx-runtime";
8
+ var SEMANTIC_COLORS = ["primary", "secondary", "success", "warning", "danger", "inherit"];
9
+ var RsIcon = forwardRef(
10
+ ({
11
+ name,
12
+ family = "outlined",
13
+ size = "md",
14
+ color = "inherit",
15
+ fill = false,
16
+ weight = 400,
17
+ grade = 0,
18
+ interactive = false,
19
+ ariaLabel,
20
+ notification = false,
21
+ notificationColor,
22
+ className
23
+ }, ref) => {
24
+ const symbolHref = useMemo(() => {
25
+ let gradeSuffix = "";
26
+ const clamped = clampGrade(grade);
27
+ if (clamped !== 0) {
28
+ gradeSuffix = clamped > 0 ? `-grad${clamped}` : `-gradN${Math.abs(clamped)}`;
29
+ }
30
+ const fillSuffix = fill ? "-fill" : "";
31
+ const weightSuffix = weight !== 400 ? `-wght${weight}` : "";
32
+ return `#rs-icon-${family}-${name}${gradeSuffix}${fillSuffix}${weightSuffix}`;
33
+ }, [family, name, grade, fill, weight]);
34
+ const isCustomColor = !SEMANTIC_COLORS.includes(color);
35
+ const iconSize = useMemo(() => {
36
+ if (typeof size === "number") return size;
37
+ if (size === "sm") return 18;
38
+ if (size === "lg") return 32;
39
+ return 24;
40
+ }, [size]);
41
+ const rootClass = useMemo(() => {
42
+ const classes = ["rs-icon", `rs-icon--${family}`];
43
+ if (!isCustomColor) {
44
+ classes.push(`rs-icon--${color}`);
45
+ }
46
+ if (interactive) {
47
+ classes.push("rs-icon--interactive");
48
+ }
49
+ if (size === "sm") {
50
+ classes.push("rs-icon--sm");
51
+ } else if (size === "lg") {
52
+ classes.push("rs-icon--lg");
53
+ } else {
54
+ classes.push("rs-icon--md");
55
+ }
56
+ if (className) {
57
+ classes.push(className);
58
+ }
59
+ return classes.join(" ");
60
+ }, [family, color, isCustomColor, interactive, size, className]);
61
+ const rootStyle = useMemo(
62
+ () => ({
63
+ "--rs-icon-color": isCustomColor ? color : void 0,
64
+ "--rs-icon-size": `${iconSize}px`
65
+ }),
66
+ [isCustomColor, color, iconSize]
67
+ );
68
+ const ariaHidden = ariaLabel ? void 0 : true;
69
+ const role = ariaLabel ? "img" : void 0;
70
+ const notificationStyle = useMemo(
71
+ () => notificationColor ? { backgroundColor: notificationColor } : void 0,
72
+ [notificationColor]
73
+ );
74
+ const svgElement = /* @__PURE__ */ jsx(
75
+ "svg",
76
+ {
77
+ ref,
78
+ "aria-hidden": ariaHidden,
79
+ "aria-label": ariaLabel,
80
+ className: rootClass,
81
+ role,
82
+ style: rootStyle,
83
+ width: iconSize,
84
+ height: iconSize,
85
+ fill: "currentColor",
86
+ children: /* @__PURE__ */ jsx("use", { href: symbolHref })
87
+ }
88
+ );
89
+ if (notification) {
90
+ return /* @__PURE__ */ jsxs("span", { className: "rs-icon-wrap", children: [
91
+ svgElement,
92
+ /* @__PURE__ */ jsx("span", { className: "rs-icon__notification", "aria-hidden": "true", style: notificationStyle })
93
+ ] });
94
+ }
95
+ return svgElement;
96
+ }
97
+ );
98
+ RsIcon.displayName = "RsIcon";
99
+ var RsIcon_default = RsIcon;
100
+
101
+ // src/components/RsIcon/index.ts
102
+ var iconNamePattern = /^[a-z0-9_]+$/;
103
+ function rsPrimeIcon(name, options = {}) {
104
+ const family = options.family ?? "outlined";
105
+ const normalizedName = name.trim();
106
+ if (!iconNamePattern.test(normalizedName)) {
107
+ throw new Error(`Invalid Material Symbols icon name: "${name}"`);
108
+ }
109
+ return `rs-icon rs-icon--prime rs-icon--${family} rs-icon-${normalizedName}`;
110
+ }
111
+
112
+ export {
113
+ RsIcon_default,
114
+ rsPrimeIcon
115
+ };
@@ -0,0 +1,49 @@
1
+ /* src/components/RsIcon/RsIcon.css */
2
+ .rs-icon {
3
+ align-items: center;
4
+ color: var(--rs-icon-color, currentColor);
5
+ display: inline-flex;
6
+ flex: 0 0 auto;
7
+ flex-shrink: 0;
8
+ height: var(--rs-icon-size, 24px);
9
+ justify-content: center;
10
+ user-select: none;
11
+ width: var(--rs-icon-size, 24px);
12
+ }
13
+ .rs-icon--interactive {
14
+ cursor: pointer;
15
+ }
16
+ .rs-icon--primary {
17
+ color: var(--rs-color-icon-primary, #48a0d4);
18
+ }
19
+ .rs-icon--secondary {
20
+ color: var(--rs-color-icon-secondary, #000000);
21
+ }
22
+ .rs-icon--success {
23
+ color: var(--rs-color-icon-success, #4EAE01);
24
+ }
25
+ .rs-icon--warning {
26
+ color: var(--rs-color-icon-warning, #FF9001);
27
+ }
28
+ .rs-icon--danger {
29
+ color: var(--rs-color-icon-danger, #d80115);
30
+ }
31
+ .rs-icon--inherit {
32
+ color: currentColor;
33
+ }
34
+ .rs-icon-wrap {
35
+ position: relative;
36
+ display: inline-flex;
37
+ align-items: center;
38
+ justify-content: center;
39
+ vertical-align: middle;
40
+ }
41
+ .rs-icon__notification {
42
+ position: absolute;
43
+ top: 0;
44
+ right: 0;
45
+ width: 10px;
46
+ height: 10px;
47
+ border-radius: 50%;
48
+ background-color: var(--rs-color-yellow-500, #ff9001);
49
+ }
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED FILE. DO NOT EDIT MANUALLY.
2
+ import { b as RsIconProps, e as RsPrimeIconOptions } from '../../RsIcon.types-eBKVmRTF.js';
3
+ export { R as RsIconColor, a as RsIconFamily, c as RsIconSize, d as RsIconWeight } from '../../RsIcon.types-eBKVmRTF.js';
4
+ import * as react from 'react';
5
+
6
+ declare const RsIcon: react.ForwardRefExoticComponent<RsIconProps & react.RefAttributes<SVGSVGElement>>;
7
+
8
+ declare function rsPrimeIcon(name: string, options?: RsPrimeIconOptions): string;
9
+
10
+ export { RsIconProps, RsPrimeIconOptions, RsIcon as default, rsPrimeIcon };
@@ -0,0 +1,10 @@
1
+ // AUTO-GENERATED FILE. DO NOT EDIT MANUALLY.
2
+ import {
3
+ RsIcon_default,
4
+ rsPrimeIcon
5
+ } from "../../chunk-PEJOAJYL.js";
6
+ import "../../chunk-D4ZW5VUU.js";
7
+ export {
8
+ RsIcon_default as default,
9
+ rsPrimeIcon
10
+ };