@mshuja288/aksuicore 0.2.1 → 0.4.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.
@@ -3,12 +3,27 @@
3
3
  import { useEffect, useRef, useState, type ReactNode } from "react";
4
4
  import { Button } from "./Button";
5
5
 
6
+ function MenuIcon({ open }: { open: boolean }) {
7
+ return (
8
+ <svg viewBox="0 0 20 20" aria-hidden="true">
9
+ {open ? (
10
+ <path d="M5 5l10 10M15 5 5 15" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
11
+ ) : (
12
+ <path d="M3.5 5.5h13M3.5 10h13M3.5 14.5h13" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" />
13
+ )}
14
+ </svg>
15
+ );
16
+ }
17
+
6
18
  export function Topbar({
7
19
  title,
8
20
  logoSrc,
9
21
  logoAlt = "Logo",
10
22
  username = "",
11
23
  onLogout,
24
+ showMenuButton = false,
25
+ menuOpen = false,
26
+ onMenuClick,
12
27
  children,
13
28
  }: {
14
29
  title: string;
@@ -16,30 +31,45 @@ export function Topbar({
16
31
  logoAlt?: string;
17
32
  username?: string;
18
33
  onLogout?: () => void;
34
+ /** Shows a hamburger control (used by AppShell drawer on small screens). */
35
+ showMenuButton?: boolean;
36
+ menuOpen?: boolean;
37
+ onMenuClick?: () => void;
19
38
  children?: ReactNode;
20
39
  }) {
21
- const [menuOpen, setMenuOpen] = useState(false);
40
+ const [accountOpen, setAccountOpen] = useState(false);
22
41
  const menuRef = useRef<HTMLDivElement>(null);
23
42
  const initial = (username || "U").slice(0, 1).toUpperCase();
24
43
 
25
44
  useEffect(() => {
26
- if (!menuOpen) {
45
+ if (!accountOpen) {
27
46
  return;
28
47
  }
29
48
 
30
49
  function onPointerDown(event: MouseEvent) {
31
50
  if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
32
- setMenuOpen(false);
51
+ setAccountOpen(false);
33
52
  }
34
53
  }
35
54
 
36
55
  document.addEventListener("mousedown", onPointerDown);
37
56
  return () => document.removeEventListener("mousedown", onPointerDown);
38
- }, [menuOpen]);
57
+ }, [accountOpen]);
39
58
 
40
59
  return (
41
60
  <header className="topbar">
42
61
  <div className="topbar-brand">
62
+ {showMenuButton && onMenuClick ? (
63
+ <button
64
+ type="button"
65
+ className="ghost topbar-menu-button"
66
+ aria-label={menuOpen ? "Close navigation menu" : "Open navigation menu"}
67
+ aria-expanded={menuOpen}
68
+ onClick={onMenuClick}
69
+ >
70
+ <MenuIcon open={menuOpen} />
71
+ </button>
72
+ ) : null}
43
73
  {logoSrc ? (
44
74
  <img
45
75
  className="topbar-logo"
@@ -60,20 +90,20 @@ export function Topbar({
60
90
  type="button"
61
91
  className="avatar-button"
62
92
  aria-haspopup="menu"
63
- aria-expanded={menuOpen}
64
- onClick={() => setMenuOpen((open) => !open)}
93
+ aria-expanded={accountOpen}
94
+ onClick={() => setAccountOpen((open) => !open)}
65
95
  title={username || "Account"}
66
96
  >
67
97
  <span className="avatar">{initial}</span>
68
98
  </button>
69
- {menuOpen ? (
99
+ {accountOpen ? (
70
100
  <div className="avatar-menu" role="menu">
71
101
  {username ? <p className="avatar-name">{username}</p> : null}
72
102
  <Button
73
103
  variant="ghost"
74
104
  role="menuitem"
75
105
  onClick={() => {
76
- setMenuOpen(false);
106
+ setAccountOpen(false);
77
107
  onLogout();
78
108
  }}
79
109
  >
@@ -404,6 +404,135 @@ button.side-rail-add:hover {
404
404
  background: #2a3546;
405
405
  }
406
406
 
407
+ .side-rail-columns,
408
+ .side-rail-download {
409
+ display: grid;
410
+ gap: 8px;
411
+ }
412
+
413
+ button.side-rail-columns-toggle {
414
+ display: inline-flex;
415
+ align-items: center;
416
+ justify-content: center;
417
+ gap: 8px;
418
+ width: 100%;
419
+ padding: 10px 12px;
420
+ border: 1px solid var(--aks-border, #d7dde5);
421
+ border-radius: var(--aks-radius);
422
+ background: #fff;
423
+ color: var(--aks-brand);
424
+ box-shadow: none;
425
+ }
426
+
427
+ button.side-rail-columns-toggle svg {
428
+ width: 18px;
429
+ height: 18px;
430
+ }
431
+
432
+ button.side-rail-columns-toggle:hover {
433
+ background: #f5f7fa;
434
+ }
435
+
436
+ .side-rail-columns-panel {
437
+ display: grid;
438
+ gap: 8px;
439
+ padding: 8px;
440
+ border: 1px solid var(--aks-line);
441
+ border-radius: var(--aks-radius);
442
+ background: #faf9f7;
443
+ }
444
+
445
+ .side-rail-columns-actions {
446
+ display: flex;
447
+ gap: 6px;
448
+ }
449
+
450
+ .side-rail-columns-actions button {
451
+ flex: 1 1 auto;
452
+ padding: 6px 8px;
453
+ font-size: 12px;
454
+ box-shadow: none;
455
+ }
456
+
457
+ .side-rail-columns-list {
458
+ display: grid;
459
+ gap: 6px;
460
+ max-height: 220px;
461
+ overflow: auto;
462
+ }
463
+
464
+ label.side-rail-column-option {
465
+ display: flex;
466
+ align-items: center;
467
+ gap: 8px;
468
+ margin: 0;
469
+ padding: 4px 2px;
470
+ color: var(--aks-brand);
471
+ font-size: 13px;
472
+ font-weight: 500;
473
+ }
474
+
475
+ label.side-rail-column-option input {
476
+ width: auto;
477
+ min-height: 0;
478
+ margin: 0;
479
+ accent-color: var(--aks-accent);
480
+ }
481
+
482
+ button.side-rail-download-toggle {
483
+ display: inline-flex;
484
+ align-items: center;
485
+ justify-content: center;
486
+ gap: 8px;
487
+ width: 100%;
488
+ padding: 10px 12px;
489
+ border: 1px solid var(--aks-border, #d7dde5);
490
+ border-radius: var(--aks-radius);
491
+ background: #fff;
492
+ color: var(--aks-brand);
493
+ box-shadow: none;
494
+ }
495
+
496
+ button.side-rail-download-toggle svg {
497
+ width: 18px;
498
+ height: 18px;
499
+ }
500
+
501
+ button.side-rail-download-toggle:hover:not(:disabled) {
502
+ background: #f5f7fa;
503
+ }
504
+
505
+ button.side-rail-download-toggle:disabled {
506
+ opacity: 0.55;
507
+ cursor: default;
508
+ }
509
+
510
+ .side-rail-download-formats {
511
+ display: grid;
512
+ grid-template-columns: repeat(3, minmax(0, 1fr));
513
+ gap: 6px;
514
+ }
515
+
516
+ button.side-rail-download-format {
517
+ padding: 8px 6px;
518
+ border: 1px solid var(--aks-border, #d7dde5);
519
+ border-radius: var(--aks-radius);
520
+ background: #fff;
521
+ color: var(--aks-brand);
522
+ font-size: 12px;
523
+ font-weight: 600;
524
+ box-shadow: none;
525
+ }
526
+
527
+ button.side-rail-download-format:hover:not(:disabled) {
528
+ background: #f5f7fa;
529
+ }
530
+
531
+ button.side-rail-download-format:disabled {
532
+ opacity: 0.45;
533
+ cursor: default;
534
+ }
535
+
407
536
  .side-rail-filters {
408
537
  display: grid;
409
538
  gap: 12px;
@@ -605,6 +734,65 @@ button.sort-header.active .sort-indicator {
605
734
  gap: 6px;
606
735
  }
607
736
 
737
+ .row-actions-mobile {
738
+ display: none;
739
+ position: relative;
740
+ }
741
+
742
+ button.row-action-menu-trigger {
743
+ width: 36px;
744
+ height: 32px;
745
+ padding: 0;
746
+ color: var(--aks-muted);
747
+ box-shadow: none;
748
+ }
749
+
750
+ button.row-action-menu-trigger svg {
751
+ width: 18px;
752
+ height: 18px;
753
+ }
754
+
755
+ button.row-action-menu-trigger:hover {
756
+ transform: none;
757
+ background: #f4f2ef;
758
+ color: var(--aks-brand);
759
+ box-shadow: none;
760
+ }
761
+
762
+ .row-action-menu {
763
+ position: absolute;
764
+ top: calc(100% + 4px);
765
+ right: 0;
766
+ z-index: 5;
767
+ display: grid;
768
+ min-width: 132px;
769
+ padding: 6px;
770
+ border: 1px solid var(--aks-line);
771
+ border-radius: var(--aks-radius);
772
+ background: #fff;
773
+ box-shadow: 0 12px 28px rgba(30, 39, 53, 0.14);
774
+ }
775
+
776
+ .row-action-menu button {
777
+ justify-content: flex-start;
778
+ width: 100%;
779
+ padding: 8px 10px;
780
+ font-size: 13px;
781
+ font-weight: 600;
782
+ color: var(--aks-brand);
783
+ box-shadow: none;
784
+ }
785
+
786
+ .row-action-menu button.danger:not(:disabled) {
787
+ color: #a33b2b;
788
+ }
789
+
790
+ .row-action-menu button:hover:not(:disabled) {
791
+ transform: none;
792
+ background: #f4f2ef;
793
+ box-shadow: none;
794
+ }
795
+
608
796
  button.row-action {
609
797
  padding: 4px 8px;
610
798
  font-size: 12px;
@@ -702,6 +890,10 @@ button.row-action.danger:not(:disabled):hover {
702
890
  }
703
891
 
704
892
  @media (max-width: 900px) {
893
+ .list-body {
894
+ flex-direction: column;
895
+ }
896
+
705
897
  .list-body.side-open,
706
898
  .list-body.preview-open {
707
899
  flex-direction: column;
@@ -719,15 +911,46 @@ button.row-action.danger:not(:disabled):hover {
719
911
  align-items: center;
720
912
  width: 100%;
721
913
  flex-basis: auto;
914
+ order: -1;
722
915
  }
723
916
 
724
917
  .side-rail:not(.open) .side-rail-icons {
725
918
  display: inline-flex;
919
+ flex-wrap: wrap;
726
920
  margin-left: auto;
921
+ gap: 4px;
922
+ }
923
+
924
+ .side-rail.open {
925
+ order: -1;
727
926
  }
728
927
 
729
928
  .table-card-bar {
730
929
  flex-wrap: wrap;
731
930
  }
931
+
932
+ .data-table {
933
+ min-width: 520px;
934
+ }
935
+
936
+ .row-actions-desktop {
937
+ display: none;
938
+ }
939
+
940
+ .row-actions-mobile {
941
+ display: inline-flex;
942
+ }
943
+
944
+ .data-table .actions-col {
945
+ position: sticky;
946
+ right: 0;
947
+ z-index: 1;
948
+ background: #fff;
949
+ box-shadow: -8px 0 12px rgba(30, 39, 53, 0.04);
950
+ }
951
+
952
+ .data-table thead .actions-col {
953
+ background: #f6f5f3;
954
+ }
732
955
  }
733
956
 
@@ -28,13 +28,17 @@ button.ghost:hover,
28
28
  button.app-select-trigger:hover,
29
29
  button.date-field-trigger:hover,
30
30
  button.avatar-button:hover,
31
+ button.topbar-menu-button:hover,
31
32
  button.sort-header:hover,
32
33
  button.row-action:hover,
34
+ button.row-action-menu-trigger:hover,
33
35
  button.date-picker-day:hover,
34
36
  button.side-rail-toggle:hover,
35
37
  button.side-rail-icon:hover,
38
+ button.side-rail-columns-toggle:hover,
36
39
  button.filter-clear:hover,
37
- button.snackbar-close:hover {
40
+ button.snackbar-close:hover,
41
+ button.nav-drawer-close:hover {
38
42
  transform: none;
39
43
  }
40
44
 
@@ -152,6 +156,10 @@ select:disabled {
152
156
  min-width: 0;
153
157
  }
154
158
 
159
+ button.topbar-menu-button {
160
+ flex: 0 0 auto;
161
+ }
162
+
155
163
  .topbar-logo {
156
164
  width: 40px;
157
165
  height: 40px;
@@ -33,20 +33,146 @@
33
33
  display: none;
34
34
  }
35
35
 
36
+ /* Mobile drawer — hidden on desktop */
37
+ .nav-drawer-backdrop,
38
+ .nav-drawer {
39
+ display: none;
40
+ }
41
+
42
+ button.topbar-menu-button {
43
+ display: none;
44
+ width: 40px;
45
+ height: 40px;
46
+ padding: 0;
47
+ border-radius: var(--aks-radius);
48
+ background: transparent;
49
+ color: var(--aks-brand);
50
+ box-shadow: none;
51
+ }
52
+
53
+ button.topbar-menu-button svg {
54
+ width: 22px;
55
+ height: 22px;
56
+ }
57
+
58
+ button.topbar-menu-button:hover {
59
+ transform: none;
60
+ background: #f4f2ef;
61
+ box-shadow: none;
62
+ }
63
+
36
64
  @media (max-width: 900px) {
37
65
  .app-body {
38
66
  grid-template-columns: 1fr;
39
67
  }
40
68
 
41
- .app-shell .side-nav {
69
+ .app-shell .app-body > .side-nav {
42
70
  display: none;
43
71
  }
44
72
 
45
- .app-shell .screen {
73
+ button.topbar-menu-button {
74
+ display: inline-grid;
75
+ place-items: center;
76
+ }
77
+
78
+ .app-shell.mobile-nav-drawer .screen,
79
+ .app-shell.mobile-nav-hybrid:not(.has-bottom-nav) .screen {
80
+ padding: 10px;
81
+ }
82
+
83
+ .app-shell.has-bottom-nav .screen {
46
84
  padding: 10px 10px 88px;
47
85
  }
48
86
 
49
- .bottom-nav {
87
+ /* Drawer overlay */
88
+ .nav-drawer-backdrop {
89
+ display: block;
90
+ position: fixed;
91
+ inset: 0;
92
+ z-index: 40;
93
+ border: 0;
94
+ padding: 0;
95
+ margin: 0;
96
+ background: rgba(30, 39, 53, 0.42);
97
+ opacity: 0;
98
+ pointer-events: none;
99
+ transition: opacity 160ms ease;
100
+ box-shadow: none;
101
+ }
102
+
103
+ .nav-drawer-backdrop.open {
104
+ opacity: 1;
105
+ pointer-events: auto;
106
+ }
107
+
108
+ .nav-drawer {
109
+ display: flex;
110
+ flex-direction: column;
111
+ position: fixed;
112
+ top: 0;
113
+ left: 0;
114
+ bottom: 0;
115
+ z-index: 50;
116
+ width: min(300px, calc(100vw - 48px));
117
+ background: #fff;
118
+ border-right: 1px solid var(--aks-line);
119
+ box-shadow: 12px 0 32px rgba(30, 39, 53, 0.18);
120
+ transform: translateX(-105%);
121
+ transition: transform 180ms ease;
122
+ pointer-events: none;
123
+ }
124
+
125
+ .nav-drawer.open {
126
+ transform: translateX(0);
127
+ pointer-events: auto;
128
+ }
129
+
130
+ .nav-drawer-head {
131
+ display: flex;
132
+ align-items: center;
133
+ justify-content: space-between;
134
+ gap: 12px;
135
+ flex: 0 0 auto;
136
+ padding: 14px 14px 10px;
137
+ border-bottom: 1px solid var(--aks-line);
138
+ }
139
+
140
+ .nav-drawer-title {
141
+ margin: 0;
142
+ color: var(--aks-brand);
143
+ font-size: 12px;
144
+ font-weight: 700;
145
+ letter-spacing: 0.06em;
146
+ text-transform: uppercase;
147
+ }
148
+
149
+ button.nav-drawer-close {
150
+ padding: 6px 10px;
151
+ font-size: 12px;
152
+ box-shadow: none;
153
+ }
154
+
155
+ .nav-drawer .side-nav,
156
+ .nav-drawer-nav {
157
+ display: flex !important;
158
+ flex-direction: column;
159
+ flex: 1 1 auto;
160
+ min-height: 0;
161
+ overflow: auto;
162
+ padding: 10px;
163
+ gap: 4px;
164
+ border: 0;
165
+ background: transparent;
166
+ }
167
+
168
+ .nav-drawer .side-nav button {
169
+ justify-content: flex-start;
170
+ width: 100%;
171
+ text-align: left;
172
+ }
173
+
174
+ /* Legacy / optional bottom nav */
175
+ .app-shell.has-bottom-nav .bottom-nav {
50
176
  position: fixed;
51
177
  left: 0;
52
178
  right: 0;
@@ -61,6 +187,10 @@
61
187
  box-shadow: 0 -8px 24px rgba(30, 39, 53, 0.18);
62
188
  }
63
189
 
190
+ .app-shell.mobile-nav-hybrid.has-bottom-nav .bottom-nav {
191
+ grid-template-columns: 1fr auto;
192
+ }
193
+
64
194
  .bottom-nav button {
65
195
  display: flex;
66
196
  flex-direction: column;
@@ -74,18 +204,37 @@
74
204
  font-size: 10px;
75
205
  line-height: 1.1;
76
206
  white-space: nowrap;
207
+ overflow: hidden;
208
+ text-overflow: ellipsis;
77
209
  box-shadow: none;
78
210
  }
79
211
 
212
+ .bottom-nav button.bottom-nav-current {
213
+ align-items: flex-start;
214
+ padding-left: 14px;
215
+ font-size: 12px;
216
+ font-weight: 600;
217
+ color: #fff;
218
+ opacity: 1;
219
+ }
220
+
221
+ .bottom-nav button.bottom-nav-menu {
222
+ min-width: 72px;
223
+ background: var(--aks-accent);
224
+ color: #fff;
225
+ font-size: 12px;
226
+ font-weight: 700;
227
+ }
228
+
80
229
  .bottom-nav button.active,
81
- .bottom-nav button:hover {
230
+ .bottom-nav button:hover:not(:disabled) {
82
231
  transform: none;
83
232
  background: var(--aks-accent);
84
233
  color: #fff;
85
234
  box-shadow: none;
86
235
  }
87
236
 
88
- .snackbar-host {
237
+ .app-shell.has-bottom-nav .snackbar-host {
89
238
  right: 16px;
90
239
  bottom: calc(78px + env(safe-area-inset-bottom));
91
240
  }