cynx-lib 1.3.83 → 1.3.85

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 (42) hide show
  1. package/gen.js +77 -77
  2. package/package.json +5 -1
  3. package/ui/components/Drawer.jsx +208 -208
  4. package/ui/components/Layout.jsx +37 -0
  5. package/ui/components/Pagination.jsx +145 -145
  6. package/ui/components/PrivacyBlur.jsx +603 -603
  7. package/ui/components/Sidebar.jsx +300 -134
  8. package/utils/index.cjs +5 -0
  9. package/utils/index.js +22 -20
  10. package/utils/utils/duration.js +139 -139
  11. package/utils/utils/oidc.js +214 -214
  12. package/utils/utils/realtime.js +1634 -1634
  13. package/utils/utils/session.cjs +15 -2
  14. package/utils/utils/session.js +67 -58
  15. package/utils/utils/session_manager.cjs +490 -0
  16. package/utils/utils/session_manager.js +549 -0
  17. package/utils/utils/storage.js +393 -393
  18. package/utils/utils/ui.js +111 -111
  19. package/vue/components/Badge.vue +79 -79
  20. package/vue/components/Button.vue +209 -209
  21. package/vue/components/Checkbox.vue +103 -103
  22. package/vue/components/CircularProgress.vue +383 -383
  23. package/vue/components/Divider.vue +94 -94
  24. package/vue/components/Drawer.vue +243 -243
  25. package/vue/components/InfoBadge.vue +223 -223
  26. package/vue/components/Input.vue +341 -341
  27. package/vue/components/LinearProgress.vue +268 -268
  28. package/vue/components/Modal.vue +226 -226
  29. package/vue/components/Pagination.vue +199 -199
  30. package/vue/components/Select.vue +713 -713
  31. package/vue/components/Sidebar.vue +955 -955
  32. package/vue/components/Skeleton.vue +91 -91
  33. package/vue/components/Spinner.vue +263 -263
  34. package/vue/components/StatusTag.vue +89 -89
  35. package/vue/components/TabBar.vue +176 -176
  36. package/vue/components/Table.vue +388 -388
  37. package/vue/components/TimePicker.vue +399 -399
  38. package/vue/components/Toggle.vue +115 -115
  39. package/vue/components/XTable.vue +119 -119
  40. package/vue/composables/useAutoSkeletonRows.ts +71 -71
  41. package/vue/index.d.ts +67 -67
  42. package/vue/index.js +86 -86
@@ -102,6 +102,23 @@ export default function Sidebar({
102
102
  user,
103
103
  onLogout,
104
104
  onLock,
105
+ // Footer customization & actions
106
+ footerActions,
107
+ extraFooterActions,
108
+ lockLabel = 'Блок',
109
+ logoutLabel = 'Выйти',
110
+ lockTitle,
111
+ logoutTitle,
112
+ lockIcon,
113
+ logoutIcon,
114
+ showLock = true,
115
+ showLogout = true,
116
+ userSlot,
117
+ renderUser,
118
+ footerStyle = {},
119
+ footerClassName = '',
120
+ footerActionsLayout = 'flex',
121
+ footerActionsStyle = {},
105
122
  collapsible = true,
106
123
  isCollapsed: controlledIsCollapsed,
107
124
  defaultCollapsed = false,
@@ -260,6 +277,70 @@ export default function Sidebar({
260
277
  ? 'var(--sidebar-text-muted, rgba(255, 255, 255, 0.45))'
261
278
  : 'var(--sidebar-text-muted, var(--t3, #a1a1aa))';
262
279
 
280
+ // Default footer actions (Lock / Logout)
281
+ const defaultActions = [];
282
+
283
+ if (onLock && showLock) {
284
+ defaultActions.push({
285
+ id: 'lock',
286
+ label: lockLabel,
287
+ title: lockTitle || (typeof lockLabel === 'string' ? lockLabel : 'Заблокировать'),
288
+ icon: lockIcon || (isCollapsed ? <Lock size={16} /> : <Lock size={13} />),
289
+ onClick: onLock,
290
+ variant: isDarkSidebar ? 'secondary' : 'ghost',
291
+ style: isDarkSidebar
292
+ ? {
293
+ background: 'rgba(255,255,255,0.08)',
294
+ color: 'rgba(255,255,255,0.85)',
295
+ borderColor: 'rgba(255,255,255,0.12)',
296
+ }
297
+ : {},
298
+ });
299
+ }
300
+
301
+ if (onLogout && showLogout) {
302
+ defaultActions.push({
303
+ id: 'logout',
304
+ label: logoutLabel,
305
+ title:
306
+ logoutTitle ||
307
+ (user?.username || user?.fullName
308
+ ? `${typeof logoutLabel === 'string' ? logoutLabel : 'Выйти'} (${user?.username || user?.fullName})`
309
+ : typeof logoutLabel === 'string'
310
+ ? logoutLabel
311
+ : 'Выйти'),
312
+ icon: logoutIcon || (isCollapsed ? <LogOut size={17} /> : <LogOut size={14} />),
313
+ onClick: onLogout,
314
+ variant: 'ghost',
315
+ danger: true,
316
+ style: isDarkSidebar
317
+ ? {
318
+ background: 'rgba(239, 68, 68, 0.1)',
319
+ borderColor: 'rgba(239, 68, 68, 0.2)',
320
+ }
321
+ : {},
322
+ });
323
+ }
324
+
325
+ // Resolve final actions list
326
+ let resolvedActions;
327
+ if (typeof footerActions === 'function') {
328
+ resolvedActions = footerActions(defaultActions, { isCollapsed, user, isDarkSidebar });
329
+ } else if (Array.isArray(footerActions)) {
330
+ resolvedActions = footerActions;
331
+ } else {
332
+ resolvedActions = [...defaultActions];
333
+ if (Array.isArray(extraFooterActions) && extraFooterActions.length > 0) {
334
+ resolvedActions.push(...extraFooterActions);
335
+ }
336
+ }
337
+
338
+ const finalActions = (resolvedActions || []).filter(
339
+ (act) => act && !act.hidden && act.visible !== false
340
+ );
341
+
342
+ const hasFooterContent = Boolean(user || finalActions.length > 0 || userSlot || renderUser);
343
+
263
344
  return (
264
345
  <aside
265
346
  className={sidebarClassName}
@@ -659,158 +740,243 @@ export default function Sidebar({
659
740
 
660
741
  {/* Footer Slot or Default User Profile / Actions */}
661
742
  {footerSlot ? (
662
- footerSlot
663
- ) : user ? (
743
+ typeof footerSlot === 'function'
744
+ ? footerSlot({ isCollapsed, user, actions: finalActions, isDarkSidebar })
745
+ : footerSlot
746
+ ) : hasFooterContent ? (
664
747
  <div
748
+ className={footerClassName}
665
749
  style={{
666
750
  padding: isCollapsed ? '12px 8px' : 12,
667
751
  borderTop: effectiveSidebarBorder,
668
752
  flexShrink: 0,
753
+ ...footerStyle,
669
754
  }}
670
755
  >
671
756
  {!isCollapsed ? (
672
757
  <>
673
- <div
674
- style={{
675
- fontSize: '.72rem',
676
- color: userFullNameColor,
677
- marginBottom: 8,
678
- padding: '0 8px',
679
- whiteSpace: 'nowrap',
680
- overflow: 'hidden',
681
- textOverflow: 'ellipsis',
682
- }}
683
- >
684
- {user.fullName ? (
685
- <>
686
- {user.fullName}{' '}
687
- {user.username && (
688
- <span style={{ color: userUsernameColor }}>({user.username})</span>
689
- )}
690
- </>
758
+ {userSlot ? (
759
+ typeof userSlot === 'function' ? (
760
+ userSlot({ user, isCollapsed: false, isDarkSidebar })
691
761
  ) : (
692
- user.username
693
- )}
694
- </div>
695
-
696
- <div style={{ display: 'flex', gap: 6 }}>
697
- {onLock && (
698
- <Button
699
- variant={isDarkSidebar ? 'secondary' : 'ghost'}
700
- size="md"
701
- onClick={onLock}
702
- icon={<Lock size={13} />}
703
- title="Заблокировать"
704
- style={{
705
- flex: 1,
706
- justifyContent: 'center',
707
- ...(isDarkSidebar
708
- ? {
709
- background: 'rgba(255,255,255,0.08)',
710
- color: 'rgba(255,255,255,0.85)',
711
- borderColor: 'rgba(255,255,255,0.12)',
712
- }
713
- : {}),
714
- }}
715
- >
716
- Блок
717
- </Button>
718
- )}
719
-
720
- {onLogout && (
721
- <Button
722
- variant="ghost"
723
- size="md"
724
- onClick={onLogout}
725
- icon={<LogOut size={14} />}
726
- title="Выйти"
727
- style={{
728
- width: onLock ? 'auto' : '100%',
729
- flex: onLock ? 1 : undefined,
730
- justifyContent: 'center',
731
- color: isDarkSidebar ? '#f87171' : 'var(--danger, #ef4444)',
732
- ...(isDarkSidebar
733
- ? {
734
- background: 'rgba(239, 68, 68, 0.1)',
735
- borderColor: 'rgba(239, 68, 68, 0.2)',
736
- }
737
- : {}),
738
- }}
739
- >
740
- Выйти
741
- </Button>
742
- )}
743
- </div>
744
- </>
745
- ) : (
746
- <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6 }}>
747
- {onLock && (
748
- <button
749
- type="button"
750
- onClick={onLock}
751
- title="Заблокировать экран"
762
+ userSlot
763
+ )
764
+ ) : renderUser ? (
765
+ renderUser({ user, isCollapsed: false, isDarkSidebar })
766
+ ) : user ? (
767
+ <div
752
768
  style={{
753
- width: 36,
754
- height: 36,
755
- display: 'flex',
756
- alignItems: 'center',
757
- justifyContent: 'center',
758
- background: 'transparent',
759
- border: 'none',
760
- borderRadius: 'var(--rads, 8px)',
761
- color: isDarkSidebar ? 'rgba(255,255,255,0.7)' : 'var(--t2, #71717a)',
762
- cursor: 'pointer',
763
- transition: 'all .14s',
764
- }}
765
- onMouseEnter={(e) => {
766
- e.currentTarget.style.background = isDarkSidebar
767
- ? 'rgba(255,255,255,0.08)'
768
- : 'var(--grey-1, #fafafa)';
769
- e.currentTarget.style.color = isDarkSidebar
770
- ? '#ffffff'
771
- : 'var(--primary-text, #09090b)';
772
- }}
773
- onMouseLeave={(e) => {
774
- e.currentTarget.style.background = 'transparent';
775
- e.currentTarget.style.color = isDarkSidebar
776
- ? 'rgba(255,255,255,0.7)'
777
- : 'var(--t2, #71717a)';
769
+ fontSize: '.72rem',
770
+ color: userFullNameColor,
771
+ marginBottom: 8,
772
+ padding: '0 8px',
773
+ whiteSpace: 'nowrap',
774
+ overflow: 'hidden',
775
+ textOverflow: 'ellipsis',
778
776
  }}
779
777
  >
780
- <Lock size={16} />
781
- </button>
782
- )}
778
+ {user.fullName ? (
779
+ <>
780
+ {user.fullName}{' '}
781
+ {user.username && (
782
+ <span style={{ color: userUsernameColor }}>({user.username})</span>
783
+ )}
784
+ </>
785
+ ) : (
786
+ user.username
787
+ )}
788
+ </div>
789
+ ) : null}
783
790
 
784
- {onLogout && (
785
- <button
786
- type="button"
787
- onClick={onLogout}
788
- title={`Выйти (${user?.username || user?.fullName || ''})`}
791
+ {finalActions.length > 0 && (
792
+ <div
789
793
  style={{
790
- width: 36,
791
- height: 36,
792
- display: 'flex',
793
- alignItems: 'center',
794
- justifyContent: 'center',
795
- background: 'transparent',
796
- border: 'none',
797
- borderRadius: 'var(--rads, 8px)',
798
- color: isDarkSidebar ? '#f87171' : 'var(--danger, #ef4444)',
799
- cursor: 'pointer',
800
- transition: 'all .14s',
801
- }}
802
- onMouseEnter={(e) => {
803
- e.currentTarget.style.background = isDarkSidebar
804
- ? 'rgba(239,68,68,0.2)'
805
- : 'var(--danger-bg, rgba(239,68,68,.10))';
806
- }}
807
- onMouseLeave={(e) => {
808
- e.currentTarget.style.background = 'transparent';
794
+ ...(footerActionsLayout === 'column'
795
+ ? { display: 'flex', flexDirection: 'column', gap: 6, width: '100%' }
796
+ : footerActionsLayout === 'grid'
797
+ ? {
798
+ display: 'grid',
799
+ gridTemplateColumns: `repeat(${finalActions.length > 2 ? 2 : finalActions.length}, minmax(0, 1fr))`,
800
+ gap: 6,
801
+ width: '100%',
802
+ }
803
+ : {
804
+ display: 'flex',
805
+ gap: 6,
806
+ flexWrap: 'wrap',
807
+ width: '100%',
808
+ }),
809
+ ...footerActionsStyle,
809
810
  }}
810
811
  >
811
- <LogOut size={17} />
812
- </button>
812
+ {finalActions.map((act, index) => {
813
+ const key = act.id || act.key || index;
814
+ if (act.render) {
815
+ return (
816
+ <React.Fragment key={key}>
817
+ {act.render({ action: act, isCollapsed: false, isDarkSidebar, user })}
818
+ </React.Fragment>
819
+ );
820
+ }
821
+
822
+ const isDanger = Boolean(act.danger || act.variant === 'danger');
823
+ const actionLabel =
824
+ act.label !== undefined
825
+ ? act.label
826
+ : act.text !== undefined
827
+ ? act.text
828
+ : act.title;
829
+ const actionTitle =
830
+ act.title || (typeof actionLabel === 'string' ? actionLabel : undefined);
831
+ const isSingle = finalActions.length === 1;
832
+
833
+ return (
834
+ <Button
835
+ key={key}
836
+ variant={
837
+ act.variant || (isDanger ? 'ghost' : isDarkSidebar ? 'secondary' : 'ghost')
838
+ }
839
+ size={act.size || 'md'}
840
+ onClick={act.onClick}
841
+ disabled={act.disabled}
842
+ icon={act.icon}
843
+ title={actionTitle}
844
+ className={act.className || ''}
845
+ style={{
846
+ flex: act.flex !== undefined ? act.flex : 1,
847
+ width: isSingle ? '100%' : (act.width || 'auto'),
848
+ justifyContent: act.justifyContent || 'center',
849
+ minWidth: 0,
850
+ whiteSpace: 'nowrap',
851
+ ...(isDanger
852
+ ? {
853
+ color: isDarkSidebar ? '#f87171' : 'var(--danger, #ef4444)',
854
+ ...(isDarkSidebar
855
+ ? {
856
+ background: 'rgba(239, 68, 68, 0.1)',
857
+ borderColor: 'rgba(239, 68, 68, 0.2)',
858
+ }
859
+ : {}),
860
+ }
861
+ : isDarkSidebar &&
862
+ (!act.variant || act.variant === 'secondary' || act.variant === 'ghost')
863
+ ? {
864
+ background: 'rgba(255,255,255,0.08)',
865
+ color: 'rgba(255,255,255,0.85)',
866
+ borderColor: 'rgba(255,255,255,0.12)',
867
+ }
868
+ : {}),
869
+ ...act.style,
870
+ }}
871
+ >
872
+ {actionLabel}
873
+ </Button>
874
+ );
875
+ })}
876
+ </div>
813
877
  )}
878
+ </>
879
+ ) : (
880
+ <div
881
+ style={{
882
+ display: 'flex',
883
+ flexDirection: 'column',
884
+ alignItems: 'center',
885
+ gap: 6,
886
+ width: '100%',
887
+ }}
888
+ >
889
+ {userSlot &&
890
+ typeof userSlot === 'function' &&
891
+ userSlot({ user, isCollapsed: true, isDarkSidebar })}
892
+ {renderUser && renderUser({ user, isCollapsed: true, isDarkSidebar })}
893
+ {finalActions.map((act, index) => {
894
+ const key = act.id || act.key || index;
895
+ if (act.render) {
896
+ return (
897
+ <React.Fragment key={key}>
898
+ {act.render({ action: act, isCollapsed: true, isDarkSidebar, user })}
899
+ </React.Fragment>
900
+ );
901
+ }
902
+
903
+ const isDanger = Boolean(act.danger || act.variant === 'danger');
904
+ const actionLabel =
905
+ act.label !== undefined
906
+ ? act.label
907
+ : act.text !== undefined
908
+ ? act.text
909
+ : act.title;
910
+ const actionTitle =
911
+ act.title || (typeof actionLabel === 'string' ? actionLabel : undefined);
912
+ const actionIcon =
913
+ act.collapsedIcon ||
914
+ act.icon ||
915
+ (typeof actionLabel === 'string' ? actionLabel.slice(0, 2) : null);
916
+
917
+ const normalColor = isDanger
918
+ ? isDarkSidebar
919
+ ? '#f87171'
920
+ : 'var(--danger, #ef4444)'
921
+ : isDarkSidebar
922
+ ? 'rgba(255,255,255,0.7)'
923
+ : 'var(--t2, #71717a)';
924
+
925
+ const hoverBg = isDanger
926
+ ? isDarkSidebar
927
+ ? 'rgba(239,68,68,0.2)'
928
+ : 'var(--danger-bg, rgba(239,68,68,.10))'
929
+ : isDarkSidebar
930
+ ? 'rgba(255,255,255,0.08)'
931
+ : 'var(--grey-1, #fafafa)';
932
+
933
+ const hoverColor = isDanger
934
+ ? isDarkSidebar
935
+ ? '#fca5a5'
936
+ : 'var(--danger, #ef4444)'
937
+ : isDarkSidebar
938
+ ? '#ffffff'
939
+ : 'var(--primary-text, #09090b)';
940
+
941
+ return (
942
+ <button
943
+ key={key}
944
+ type="button"
945
+ onClick={act.onClick}
946
+ disabled={act.disabled}
947
+ title={actionTitle}
948
+ aria-label={actionTitle}
949
+ className={act.className || ''}
950
+ style={{
951
+ width: 36,
952
+ height: 36,
953
+ display: 'flex',
954
+ alignItems: 'center',
955
+ justifyContent: 'center',
956
+ background: 'transparent',
957
+ border: 'none',
958
+ borderRadius: 'var(--rads, 8px)',
959
+ color: normalColor,
960
+ cursor: act.disabled ? 'not-allowed' : 'pointer',
961
+ opacity: act.disabled ? 0.5 : 1,
962
+ transition: 'all .14s',
963
+ ...act.collapsedStyle,
964
+ ...act.style,
965
+ }}
966
+ onMouseEnter={(e) => {
967
+ if (act.disabled) return;
968
+ e.currentTarget.style.background = hoverBg;
969
+ e.currentTarget.style.color = hoverColor;
970
+ }}
971
+ onMouseLeave={(e) => {
972
+ e.currentTarget.style.background = 'transparent';
973
+ e.currentTarget.style.color = normalColor;
974
+ }}
975
+ >
976
+ {actionIcon}
977
+ </button>
978
+ );
979
+ })}
814
980
  </div>
815
981
  )}
816
982
  </div>
package/utils/index.cjs CHANGED
@@ -32,6 +32,7 @@ __export(index_exports, {
32
32
  durationUtil: () => import_duration.default,
33
33
  oidcUtil: () => import_oidc.default,
34
34
  realtimeUtil: () => import_realtime.default,
35
+ sessionManagerUtil: () => import_session_manager.default,
35
36
  sessionUtil: () => import_session.default,
36
37
  storageUtil: () => import_storage.default,
37
38
  tzUtil: () => import_tz.default,
@@ -46,6 +47,7 @@ __reExport(index_exports, require("./utils/oidc.cjs"), module.exports);
46
47
  __reExport(index_exports, require("./utils/storage.cjs"), module.exports);
47
48
  __reExport(index_exports, require("./utils/duration.cjs"), module.exports);
48
49
  __reExport(index_exports, require("./utils/session.cjs"), module.exports);
50
+ __reExport(index_exports, require("./utils/session_manager.cjs"), module.exports);
49
51
  __reExport(index_exports, require("./utils/ui.cjs"), module.exports);
50
52
  __reExport(index_exports, require("./utils/db.cjs"), module.exports);
51
53
  var import_uid = __toESM(require("./utils/uid.cjs"), 1);
@@ -55,6 +57,7 @@ var import_oidc = __toESM(require("./utils/oidc.cjs"), 1);
55
57
  var import_storage = __toESM(require("./utils/storage.cjs"), 1);
56
58
  var import_duration = __toESM(require("./utils/duration.cjs"), 1);
57
59
  var import_session = __toESM(require("./utils/session.cjs"), 1);
60
+ var import_session_manager = __toESM(require("./utils/session_manager.cjs"), 1);
58
61
  var import_ui = __toESM(require("./utils/ui.cjs"), 1);
59
62
  var import_db = __toESM(require("./utils/db.cjs"), 1);
60
63
  // Annotate the CommonJS export names for ESM import in node:
@@ -63,6 +66,7 @@ var import_db = __toESM(require("./utils/db.cjs"), 1);
63
66
  durationUtil,
64
67
  oidcUtil,
65
68
  realtimeUtil,
69
+ sessionManagerUtil,
66
70
  sessionUtil,
67
71
  storageUtil,
68
72
  tzUtil,
@@ -75,6 +79,7 @@ var import_db = __toESM(require("./utils/db.cjs"), 1);
75
79
  ...require("./utils/storage.cjs"),
76
80
  ...require("./utils/duration.cjs"),
77
81
  ...require("./utils/session.cjs"),
82
+ ...require("./utils/session_manager.cjs"),
78
83
  ...require("./utils/ui.cjs"),
79
84
  ...require("./utils/db.cjs")
80
85
  });
package/utils/index.js CHANGED
@@ -1,20 +1,22 @@
1
- // cynx/utils — shared utility functions
2
- export * from './utils/uid.js';
3
- export * from './utils/realtime.js';
4
- export * from './utils/tz.js';
5
- export * from './utils/oidc.js';
6
- export * from './utils/storage.js';
7
- export * from './utils/duration.js';
8
- export * from './utils/session.js';
9
- export * from './utils/ui.js';
10
- export * from './utils/db.js';
11
-
12
- export { default as uidUtil } from './utils/uid.js';
13
- export { default as realtimeUtil } from './utils/realtime.js';
14
- export { default as tzUtil } from './utils/tz.js';
15
- export { default as oidcUtil } from './utils/oidc.js';
16
- export { default as storageUtil } from './utils/storage.js';
17
- export { default as durationUtil } from './utils/duration.js';
18
- export { default as sessionUtil } from './utils/session.js';
19
- export { default as uiUtil } from './utils/ui.js';
20
- export { default as dbUtil } from './utils/db.js';
1
+ // cynx/utils — shared utility functions
2
+ export * from './utils/uid.js';
3
+ export * from './utils/realtime.js';
4
+ export * from './utils/tz.js';
5
+ export * from './utils/oidc.js';
6
+ export * from './utils/storage.js';
7
+ export * from './utils/duration.js';
8
+ export * from './utils/session.js';
9
+ export * from './utils/session_manager.js';
10
+ export * from './utils/ui.js';
11
+ export * from './utils/db.js';
12
+
13
+ export { default as uidUtil } from './utils/uid.js';
14
+ export { default as realtimeUtil } from './utils/realtime.js';
15
+ export { default as tzUtil } from './utils/tz.js';
16
+ export { default as oidcUtil } from './utils/oidc.js';
17
+ export { default as storageUtil } from './utils/storage.js';
18
+ export { default as durationUtil } from './utils/duration.js';
19
+ export { default as sessionUtil } from './utils/session.js';
20
+ export { default as sessionManagerUtil } from './utils/session_manager.js';
21
+ export { default as uiUtil } from './utils/ui.js';
22
+ export { default as dbUtil } from './utils/db.js';