@brftech/filex-core 0.28.0 → 0.30.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.
@@ -73,6 +73,23 @@ const props = defineProps<{
73
73
  theme?: ThemeMode;
74
74
  /* koru:k1 — inspector (details panel) toggle state, for the pressed style. */
75
75
  inspectorOpen?: boolean;
76
+ /**
77
+ * gezinti:g1 — navigation panel state, for the pressed style on the toggle.
78
+ * The panel has its own collapse control, but a narrow explorer renders it
79
+ * as a drawer that is off screen when closed, so the only way back to it is
80
+ * from here.
81
+ */
82
+ navOpen?: boolean;
83
+ /** gezinti:g1 — false when the deployment has no navigation panel; the
84
+ * toggle then has nothing to toggle and is not rendered. */
85
+ navEnabled?: boolean;
86
+ /**
87
+ * gezinti:g1 — view modes to offer. Absent = all three (the historical
88
+ * behaviour). `ExplorerConfig.uiProfile: 'simple'` narrows it to list+grid:
89
+ * a four-way switcher is one of the things the reporter in #14 named as
90
+ * "power user tool", and gallery is the one nobody outside photos asks for.
91
+ */
92
+ viewModes?: ViewMode[];
76
93
  }>();
77
94
 
78
95
  const emit = defineEmits<{
@@ -86,6 +103,7 @@ const emit = defineEmits<{
86
103
  (e: 'open-recents'): void;
87
104
  (e: 'update:density', v: Density): void;
88
105
  (e: 'toggle-inspector'): void /* koru:k1 */;
106
+ (e: 'toggle-nav'): void /* gezinti:g1 */;
89
107
  (e: 'open-theme'): void /* wiring:c1 — tema galerisi */;
90
108
  (e: 'open-shortcut-settings'): void /* wiring:c2 */;
91
109
  }>();
@@ -270,6 +288,12 @@ const moreRef = ref<InstanceType<typeof ContextMenu> | null>(null);
270
288
  // Everything the wide toolbar renders as standalone buttons, folded into one
271
289
  // action list: folder-level writes (New Folder / Paste), the shared
272
290
  // selection actions, then the view utilities (Refresh / density / view mode).
291
+ /* gezinti:g1 — is this view mode on offer? No prop = all of them, so every
292
+ embedder that predates uiProfile keeps the switcher it had. */
293
+ function offersView(v: ViewMode): boolean {
294
+ return !props.viewModes || props.viewModes.includes(v);
295
+ }
296
+
273
297
  const moreActions = computed<ContextAction[]>(() => {
274
298
  const list: ContextAction[] = [];
275
299
  const writable =
@@ -291,10 +315,15 @@ const moreActions = computed<ContextAction[]>(() => {
291
315
  });
292
316
  /* wiring:d2 — dar mod ⋯ menüsü: aktif olmayan İKİ görünüm de listelenir
293
317
  (list/grid/gallery); eski tekli toggle üç modda eksik kalıyordu. */
294
- if (props.viewMode !== 'list') list.push({ key: 'view-list', label: t('toolbar.view.list'), icon: '☰' });
295
- if (props.viewMode !== 'grid') list.push({ key: 'view-grid', label: t('toolbar.view.grid'), icon: '▦' });
296
- if (props.viewMode !== 'gallery') list.push({ key: 'view-gallery', label: t('toolbar.view.gallery'), icon: '▣' });
318
+ if (props.viewMode !== 'list' && offersView('list')) list.push({ key: 'view-list', label: t('toolbar.view.list'), icon: '☰' });
319
+ if (props.viewMode !== 'grid' && offersView('grid')) list.push({ key: 'view-grid', label: t('toolbar.view.grid'), icon: '▦' });
320
+ if (props.viewMode !== 'gallery' && offersView('gallery')) list.push({ key: 'view-gallery', label: t('toolbar.view.gallery'), icon: '▣' });
297
321
  /* /wiring:d2 */
322
+ /* gezinti:g1 — reachable from the overflow menu too, because the toolbar's
323
+ own toggle is hidden while the narrow search field is expanded. */
324
+ if (props.navEnabled !== false) {
325
+ list.push({ key: 'nav', label: t('toolbar.nav'), icon: '☰' });
326
+ }
298
327
  /* koru:k1 — inspector toggle also reachable from the narrow overflow menu */
299
328
  list.push({ key: 'inspector', label: t('toolbar.inspector'), icon: 'ℹ' });
300
329
  /* wiring:c1 — tema galerisi de dar modda ⋯ menüsünden açılır */
@@ -332,6 +361,9 @@ function onMoreSelect(a: ContextAction) {
332
361
  case 'inspector' /* koru:k1 */:
333
362
  emit('toggle-inspector');
334
363
  break;
364
+ case 'nav' /* gezinti:g1 */:
365
+ emit('toggle-nav');
366
+ break;
335
367
  case 'theme' /* wiring:c1 */:
336
368
  emit('open-theme');
337
369
  break;
@@ -360,6 +392,36 @@ function onMoreSelect(a: ContextAction) {
360
392
  <!-- bag:b4 — wide layout, untouched; renders exactly as before when not narrow -->
361
393
  <template v-if="!narrow">
362
394
  <div ref="primaryEl" class="fe-toolbar__primary">
395
+ <!-- gezinti:g1 — navigation panel toggle. Leftmost, the position every
396
+ Drive-shaped UI puts it in; the panel's own header carries the same
397
+ action, and in narrow mode where the panel is an off-screen drawer
398
+ this is the ONLY way back to it. -->
399
+ <button
400
+ v-if="navEnabled !== false"
401
+ type="button"
402
+ class="fe-btn fe-btn--icon-only fe-toolbar__nav"
403
+ :class="{ 'is-active': navOpen }"
404
+ :aria-pressed="!!navOpen"
405
+ :title="t('toolbar.nav')"
406
+ :aria-label="t('toolbar.nav')"
407
+ data-testid="toolbar-nav"
408
+ @click="emit('toggle-nav')"
409
+ >
410
+ <svg
411
+ class="fe-ficon"
412
+ viewBox="0 0 24 24"
413
+ fill="none"
414
+ stroke="currentColor"
415
+ stroke-width="1.8"
416
+ stroke-linecap="round"
417
+ aria-hidden="true"
418
+ focusable="false"
419
+ >
420
+ <rect x="3.5" y="4.5" width="17" height="15" rx="2" />
421
+ <path d="M9.5 4.5v15" />
422
+ </svg>
423
+ </button>
424
+
363
425
  <button
364
426
  v-if="canGoUp"
365
427
  type="button"
@@ -567,6 +629,7 @@ function onMoreSelect(a: ContextAction) {
567
629
  <div class="fe-toolbar__view" role="tablist" :aria-label="t('toolbar.view_label') /* wiring:c4 — was hardcoded EN */">
568
630
  <button
569
631
  type="button"
632
+ v-if="offersView('list')"
570
633
  class="fe-btn fe-btn--icon-only"
571
634
  :class="{ 'is-active': viewMode === 'list' }"
572
635
  role="tab"
@@ -579,6 +642,7 @@ function onMoreSelect(a: ContextAction) {
579
642
  </button>
580
643
  <button
581
644
  type="button"
645
+ v-if="offersView('grid')"
582
646
  class="fe-btn fe-btn--icon-only"
583
647
  :class="{ 'is-active': viewMode === 'grid' }"
584
648
  role="tab"
@@ -592,6 +656,7 @@ function onMoreSelect(a: ContextAction) {
592
656
  <!-- wiring:d2 — üçüncü görünüm: galeri -->
593
657
  <button
594
658
  type="button"
659
+ v-if="offersView('gallery')"
595
660
  class="fe-btn fe-btn--icon-only"
596
661
  :class="{ 'is-active': viewMode === 'gallery' }"
597
662
  role="tab"
@@ -632,6 +697,36 @@ function onMoreSelect(a: ContextAction) {
632
697
  </button>
633
698
  </template>
634
699
  <template v-else>
700
+ <!-- gezinti:g1 — navigation panel toggle. Leftmost, the position every
701
+ Drive-shaped UI puts it in; the panel's own header carries the same
702
+ action, and in narrow mode where the panel is an off-screen drawer
703
+ this is the ONLY way back to it. -->
704
+ <button
705
+ v-if="navEnabled !== false"
706
+ type="button"
707
+ class="fe-btn fe-btn--icon-only fe-toolbar__nav"
708
+ :class="{ 'is-active': navOpen }"
709
+ :aria-pressed="!!navOpen"
710
+ :title="t('toolbar.nav')"
711
+ :aria-label="t('toolbar.nav')"
712
+ data-testid="toolbar-nav"
713
+ @click="emit('toggle-nav')"
714
+ >
715
+ <svg
716
+ class="fe-ficon"
717
+ viewBox="0 0 24 24"
718
+ fill="none"
719
+ stroke="currentColor"
720
+ stroke-width="1.8"
721
+ stroke-linecap="round"
722
+ aria-hidden="true"
723
+ focusable="false"
724
+ >
725
+ <rect x="3.5" y="4.5" width="17" height="15" rx="2" />
726
+ <path d="M9.5 4.5v15" />
727
+ </svg>
728
+ </button>
729
+
635
730
  <button
636
731
  v-if="canGoUp"
637
732
  type="button"
@@ -19,14 +19,22 @@ export function stripAdapter(p: string): string {
19
19
 
20
20
  /**
21
21
  * Hide system/internal entries the user must never see as files:
22
- * thumbnails, version history, the soft-delete store, keepdir markers
23
- * and the E2E marker. Shared by both panes' listing filters.
22
+ * thumbnails, version history, the soft-delete store, keepdir markers,
23
+ * the desktop app's open-with scratch area and the E2E marker. Shared by
24
+ * both panes' listing filters.
24
25
  */
25
26
  export function filterInternalEntries(files: FileNode[]): FileNode[] {
26
27
  return (files || []).filter((f) => {
27
28
  if (f.path.includes('.thumbs')) return false;
28
29
  if (f.path.includes('.versions') || f.basename === '.versions') return false;
29
30
  if (f.basename === '.trash') return false;
31
+ // The desktop app's "open this document with filex" scratch area. Real
32
+ // files live in it for the length of one editing session, but they are the
33
+ // app's plumbing, not the user's documents -- and a user who has switched
34
+ // hidden files on is asking to see their OWN dotfiles, not ours. Hidden
35
+ // absolutely, like .filex-trash and .versions; stale copies are swept by
36
+ // the desktop app itself, not by hand.
37
+ if (f.basename === '.filex-open') return false;
30
38
  if (f.basename === '.keepdir') return false;
31
39
  if (f.basename === E2E_MARKER_NAME) return false;
32
40
  return true;
package/src/locales/en.ts CHANGED
@@ -3,7 +3,7 @@ export const en: Record<string, string> = {
3
3
  'toolbar.new_folder': 'New Folder',
4
4
  'toolbar.upload': 'Upload',
5
5
  'toolbar.search': 'Search',
6
- 'toolbar.search.placeholder': 'File name',
6
+ 'toolbar.search.placeholder': 'Search by name or tag:…',
7
7
  'toolbar.view.list': 'List',
8
8
  'toolbar.view.grid': 'Grid',
9
9
  'toolbar.refresh': 'Refresh',
@@ -342,7 +342,7 @@ export const en: Record<string, string> = {
342
342
  'Pick files with this button — or simply drag & drop them anywhere in the window.',
343
343
  'tour.step.search.title': 'Search',
344
344
  'tour.step.search.desc':
345
- 'This box filters the current folder. The Ctrl+K command palette searches across all storages and runs commands.',
345
+ 'This box searches the whole storage by name — separators and one typo are forgiven, so "invoice 2026" finds invoice_2026.pdf. Add tag:invoice to filter by tag. The Ctrl+K command palette also runs commands.',
346
346
  'tour.step.view.title': 'Switch views',
347
347
  'tour.step.view.desc':
348
348
  'Toggle between the list and grid layout; your choice is remembered.',
@@ -840,4 +840,38 @@ export const en: Record<string, string> = {
840
840
  'storages.fields.url': 'Base URL',
841
841
  'storages.fields.user': 'User',
842
842
 
843
+
844
+ /* === gezinti:g1 — navigation panel (SideNav) === */
845
+ 'sidenav.title': 'Navigation',
846
+ 'sidenav.views': 'Views',
847
+ 'sidenav.storages': 'Storages',
848
+ 'sidenav.recent': 'Recent',
849
+ 'sidenav.starred': 'Starred',
850
+ 'sidenav.shared': 'Shared with me',
851
+ 'sidenav.trash': 'Trash',
852
+ 'sidenav.collapse': 'Collapse navigation',
853
+ 'sidenav.expand': 'Expand navigation',
854
+ 'sidenav.close': 'Close navigation',
855
+ 'sidenav.storage.shared': 'Shared',
856
+ 'toolbar.nav': 'Navigation',
857
+ 'node.recent': 'Recent',
858
+ 'node.starred': 'Starred',
859
+ 'node.shared': 'Shared with me',
860
+ 'empty.recent.title': 'Nothing opened yet',
861
+ 'empty.recent.hint': 'Files you open show up here',
862
+ 'empty.starred.title': 'Nothing starred yet',
863
+ 'empty.starred.hint': 'Star a file to find it here',
864
+ 'empty.shared.title': 'Nothing has been shared with you',
865
+ 'empty.shared.hint': 'Folders and files other people share appear here',
866
+ 'sidenav.connections': 'Connections',
867
+ 'sidenav.connect': 'How to connect',
868
+ 'sidenav.apikeys': 'API keys',
869
+ 'overlay.close': 'Close',
870
+ 'conn.tokens.scopes': 'Permissions',
871
+ 'conn.tokens.root': 'Limit to one folder (optional)',
872
+ 'conn.tokens.rootPlaceholder': 'storage://folder',
873
+ 'conn.tokens.expiry': 'Expires in (days)',
874
+ 'conn.tokens.expiryNever': 'never',
875
+ 'conn.tokens.capNote': 'Your account and its permissions are the ceiling — asking for more than you have is refused, not granted.',
876
+ /* === /gezinti:g1 === */
843
877
  };
package/src/locales/tr.ts CHANGED
@@ -3,7 +3,7 @@ export const tr: Record<string, string> = {
3
3
  'toolbar.new_folder': 'Yeni Klasör',
4
4
  'toolbar.upload': 'Yükle',
5
5
  'toolbar.search': 'Ara',
6
- 'toolbar.search.placeholder': 'Dosya adı…',
6
+ 'toolbar.search.placeholder': 'Ada ya da tag: ile ara…',
7
7
  'toolbar.view.list': 'Liste',
8
8
  'toolbar.view.grid': 'Izgara',
9
9
  'toolbar.refresh': 'Yenile',
@@ -342,7 +342,7 @@ export const tr: Record<string, string> = {
342
342
  'Bu düğmeyle dosya seçebilirsin; dosyaları doğrudan pencereye sürükleyip bırakmak da çalışır.',
343
343
  'tour.step.search.title': 'Arama',
344
344
  'tour.step.search.desc':
345
- 'Bu kutu bulunduğun klasörü filtreler. Ctrl+K komut paleti ile tüm depolarda arama yapabilir ve komut çalıştırabilirsin.',
345
+ 'Bu kutu deponun tamamında ada göre arar; ayraçları ve bir harflik yazım hatasını affeder, yani "fatura 2026" yazdığında fatura_2026.pdf gelir. tag:fatura yazarak etikete göre süzebilirsin. Ctrl+K komut paleti ayrıca komut da çalıştırır.',
346
346
  'tour.step.view.title': 'Görünümü değiştir',
347
347
  'tour.step.view.desc':
348
348
  'Liste ve ızgara görünümü arasında geçiş yapabilirsin; seçimin hatırlanır.',
@@ -843,4 +843,38 @@ export const tr: Record<string, string> = {
843
843
  'storages.fields.url': 'Temel URL',
844
844
  'storages.fields.user': 'Kullanıcı',
845
845
 
846
+
847
+ /* === gezinti:g1 — gezinti paneli (SideNav) === */
848
+ 'sidenav.title': 'Gezinti',
849
+ 'sidenav.views': 'Görünümler',
850
+ 'sidenav.storages': 'Depolar',
851
+ 'sidenav.recent': 'Son kullanılanlar',
852
+ 'sidenav.starred': 'Yıldızlılar',
853
+ 'sidenav.shared': 'Benimle paylaşılanlar',
854
+ 'sidenav.trash': 'Çöp Kutusu',
855
+ 'sidenav.collapse': 'Gezintiyi daralt',
856
+ 'sidenav.expand': 'Gezintiyi genişlet',
857
+ 'sidenav.close': 'Gezintiyi kapat',
858
+ 'sidenav.storage.shared': 'Paylaşılan',
859
+ 'toolbar.nav': 'Gezinti',
860
+ 'node.recent': 'Son kullanılanlar',
861
+ 'node.starred': 'Yıldızlılar',
862
+ 'node.shared': 'Benimle paylaşılanlar',
863
+ 'empty.recent.title': 'Henüz hiçbir şey açılmadı',
864
+ 'empty.recent.hint': 'Açtığın dosyalar burada görünür',
865
+ 'empty.starred.title': 'Henüz yıldızlı dosya yok',
866
+ 'empty.starred.hint': 'Bir dosyayı yıldızla, burada bulasın',
867
+ 'empty.shared.title': 'Seninle henüz bir şey paylaşılmadı',
868
+ 'empty.shared.hint': 'Başkalarının paylaştığı klasör ve dosyalar burada görünür',
869
+ 'sidenav.connections': 'Bağlantılar',
870
+ 'sidenav.connect': 'Nasıl bağlanılır',
871
+ 'sidenav.apikeys': 'API anahtarları',
872
+ 'overlay.close': 'Kapat',
873
+ 'conn.tokens.scopes': 'İzinler',
874
+ 'conn.tokens.root': 'Tek bir klasörle sınırla (isteğe bağlı)',
875
+ 'conn.tokens.rootPlaceholder': 'depo://klasör',
876
+ 'conn.tokens.expiry': 'Geçerlilik (gün)',
877
+ 'conn.tokens.expiryNever': 'süresiz',
878
+ 'conn.tokens.capNote': 'Tavan hesabın ve yetkilerin — sahip olduğundan fazlasını istemek reddedilir, verilmez.',
879
+ /* === /gezinti:g1 === */
846
880
  };
@@ -4010,3 +4010,363 @@ filex-explorer {
4010
4010
  background: var(--fe-primary);
4011
4011
  transition: width 0.3s ease;
4012
4012
  }
4013
+
4014
+ /* === gezinti:g1 — SideNav (navigation panel) ==========================
4015
+ A left column inside .fe__main, the mirror of .fe-inspector on the right:
4016
+ same flex slot, same overlay trick when the explorer is narrow. .fe__primary
4017
+ already carries `flex: 1 1 auto; min-width: 0`, so it absorbs the width
4018
+ change with no rule of its own. */
4019
+ .fe-sidenav {
4020
+ flex: 0 0 232px;
4021
+ width: 232px;
4022
+ min-height: 0;
4023
+ display: flex;
4024
+ flex-direction: column;
4025
+ border-right: 1px solid var(--fe-border);
4026
+ /* No `background: var(--fe-bg)` here on purpose — see .fe-conn. This one IS
4027
+ inside the full-screen explorer, whose root already paints --fe-bg, so a
4028
+ second identical fill would only be a second chance to disagree with it. */
4029
+ color: var(--fe-text);
4030
+ transition: flex-basis 0.14s ease, width 0.14s ease;
4031
+ }
4032
+ /* Collapsed: an icon rail, not nothing. A panel that disappears takes its own
4033
+ re-open affordance with it. Width is one 32px control plus its padding. */
4034
+ .fe-sidenav--rail {
4035
+ flex: 0 0 56px;
4036
+ width: 56px;
4037
+ }
4038
+ .fe-sidenav__head {
4039
+ flex: 0 0 auto;
4040
+ display: flex;
4041
+ align-items: center;
4042
+ /* Same height as .fe-breadcrumb (37px) so the panel head and the crumb bar
4043
+ sit on one line across the top of .fe__main. */
4044
+ min-height: 37px;
4045
+ padding: 4px 8px;
4046
+ border-bottom: 1px solid var(--fe-border);
4047
+ }
4048
+ .fe-sidenav--rail .fe-sidenav__head {
4049
+ justify-content: center;
4050
+ padding: 4px;
4051
+ }
4052
+ .fe-sidenav__toggle {
4053
+ display: inline-flex;
4054
+ align-items: center;
4055
+ justify-content: center;
4056
+ width: 30px;
4057
+ height: 28px;
4058
+ padding: 0;
4059
+ border: 1px solid transparent;
4060
+ border-radius: var(--fe-radius-sm);
4061
+ background: transparent;
4062
+ color: var(--fe-text-muted);
4063
+ font: inherit;
4064
+ font-size: 17px;
4065
+ cursor: pointer;
4066
+ }
4067
+ .fe-sidenav__toggle:hover {
4068
+ background: var(--fe-bg-hover);
4069
+ color: var(--fe-text);
4070
+ }
4071
+ .fe-sidenav__toggle:focus-visible,
4072
+ .fe-sidenav__item:focus-visible,
4073
+ .fe-sidenav__upload:focus-visible,
4074
+ .fe-sidenav__secondary:focus-visible {
4075
+ outline: 2px solid var(--fe-primary);
4076
+ outline-offset: 1px;
4077
+ }
4078
+
4079
+ /* Primary action block. */
4080
+ .fe-sidenav__primary {
4081
+ flex: 0 0 auto;
4082
+ display: flex;
4083
+ flex-direction: column;
4084
+ gap: 6px;
4085
+ padding: 10px 10px 6px;
4086
+ }
4087
+ .fe-sidenav--rail .fe-sidenav__primary {
4088
+ padding: 10px 8px 6px;
4089
+ align-items: center;
4090
+ }
4091
+ .fe-sidenav__upload,
4092
+ .fe-sidenav__secondary {
4093
+ display: inline-flex;
4094
+ align-items: center;
4095
+ gap: 10px;
4096
+ width: 100%;
4097
+ padding: 9px 12px;
4098
+ border-radius: 999px;
4099
+ border: 1px solid transparent;
4100
+ font: inherit;
4101
+ font-size: 13.5px;
4102
+ cursor: pointer;
4103
+ text-align: left;
4104
+ white-space: nowrap;
4105
+ overflow: hidden;
4106
+ }
4107
+ .fe-sidenav__upload {
4108
+ background: var(--fe-primary);
4109
+ border-color: var(--fe-primary);
4110
+ color: var(--fe-text-on-primary);
4111
+ font-weight: 600;
4112
+ box-shadow: var(--fe-shadow-sm);
4113
+ }
4114
+ .fe-sidenav__upload:hover:not(:disabled) {
4115
+ background: var(--fe-primary-hover);
4116
+ border-color: var(--fe-primary-hover);
4117
+ }
4118
+ .fe-sidenav__secondary {
4119
+ background: transparent;
4120
+ border-color: var(--fe-border);
4121
+ color: var(--fe-text);
4122
+ }
4123
+ .fe-sidenav__secondary:hover:not(:disabled) {
4124
+ background: var(--fe-bg-hover);
4125
+ border-color: var(--fe-border-strong);
4126
+ }
4127
+ .fe-sidenav__upload:disabled,
4128
+ .fe-sidenav__secondary:disabled {
4129
+ opacity: 0.45;
4130
+ cursor: not-allowed;
4131
+ }
4132
+ .fe-sidenav__upload .fe-ficon,
4133
+ .fe-sidenav__secondary .fe-ficon,
4134
+ .fe-sidenav__item .fe-ficon {
4135
+ font-size: 17px;
4136
+ }
4137
+ /* On the rail the two buttons become squares — a pill with no label reads as
4138
+ a stretched blob. */
4139
+ .fe-sidenav--rail .fe-sidenav__upload,
4140
+ .fe-sidenav--rail .fe-sidenav__secondary {
4141
+ width: 38px;
4142
+ height: 38px;
4143
+ padding: 0;
4144
+ justify-content: center;
4145
+ border-radius: var(--fe-radius-md);
4146
+ }
4147
+
4148
+ .fe-sidenav__scroll {
4149
+ flex: 1 1 auto;
4150
+ min-height: 0;
4151
+ overflow-y: auto;
4152
+ padding: 4px 8px 10px;
4153
+ }
4154
+ .fe-sidenav__group {
4155
+ list-style: none;
4156
+ margin: 0;
4157
+ padding: 0;
4158
+ }
4159
+ .fe-sidenav__item {
4160
+ display: flex;
4161
+ align-items: center;
4162
+ gap: 10px;
4163
+ width: 100%;
4164
+ padding: 7px 10px;
4165
+ margin: 1px 0;
4166
+ border: 0;
4167
+ border-radius: 999px;
4168
+ background: transparent;
4169
+ color: var(--fe-text);
4170
+ font: inherit;
4171
+ font-size: 13.5px;
4172
+ cursor: pointer;
4173
+ text-align: left;
4174
+ white-space: nowrap;
4175
+ overflow: hidden;
4176
+ }
4177
+ .fe-sidenav__item:hover {
4178
+ background: var(--fe-bg-hover);
4179
+ }
4180
+ .fe-sidenav__item.is-active {
4181
+ background: var(--fe-bg-selected);
4182
+ font-weight: 600;
4183
+ }
4184
+ .fe-sidenav--rail .fe-sidenav__item {
4185
+ justify-content: center;
4186
+ padding: 8px 0;
4187
+ border-radius: var(--fe-radius-md);
4188
+ }
4189
+ .fe-sidenav__text {
4190
+ overflow: hidden;
4191
+ text-overflow: ellipsis;
4192
+ }
4193
+ .fe-sidenav__section {
4194
+ margin-top: 10px;
4195
+ }
4196
+ .fe-sidenav__heading {
4197
+ margin: 4px 0 2px;
4198
+ padding: 0 10px;
4199
+ font-size: 11px;
4200
+ font-weight: 600;
4201
+ letter-spacing: 0.04em;
4202
+ text-transform: uppercase;
4203
+ color: var(--fe-text-muted);
4204
+ }
4205
+ .fe-sidenav__rule {
4206
+ margin: 8px 6px;
4207
+ border: 0;
4208
+ border-top: 1px solid var(--fe-border);
4209
+ }
4210
+ /* "Shared" marker on a storage the caller only reaches through a grant. */
4211
+ .fe-sidenav__tag {
4212
+ margin-left: auto;
4213
+ flex: 0 0 auto;
4214
+ padding: 1px 6px;
4215
+ border-radius: 999px;
4216
+ background: color-mix(in srgb, var(--fe-primary) 16%, transparent);
4217
+ color: var(--fe-primary);
4218
+ font-size: 10px;
4219
+ font-weight: 600;
4220
+ letter-spacing: 0.02em;
4221
+ }
4222
+
4223
+ /* Narrow (<560px): a drawer over the listing, never a column. At 390px a
4224
+ 232px column leaves 158px for the files, and a 56px rail still takes an
4225
+ eighth of the screen from a listing that is already tight. */
4226
+ .fe-sidenav--drawer {
4227
+ position: absolute;
4228
+ top: 0;
4229
+ bottom: 0;
4230
+ left: 0;
4231
+ width: min(280px, 84%);
4232
+ flex: none;
4233
+ /* Same layer as .fe-inspector--overlay: above the listing and the FAB (45),
4234
+ below the context menus (90) and the onboarding tour (96), which are
4235
+ appended to <body> and would paint over anything lower anyway. */
4236
+ z-index: 60;
4237
+ background: var(--fe-bg);
4238
+ border-right: 1px solid var(--fe-border);
4239
+ box-shadow: var(--fe-shadow);
4240
+ }
4241
+ .fe-sidenav__scrim {
4242
+ position: absolute;
4243
+ inset: 0;
4244
+ z-index: 59;
4245
+ border: 0;
4246
+ padding: 0;
4247
+ background: rgba(13, 19, 27, 0.42);
4248
+ cursor: pointer;
4249
+ }
4250
+ /* === /gezinti:g1 === */
4251
+
4252
+ /* --- gezinti:g1 — the panel's Connections / API-keys overlay --- */
4253
+ /* ⚠ z-index 130. The explorer's onboarding tour (96) and its teleported menus
4254
+ (90) are appended to <body> and are `fixed`, so they paint over anything in
4255
+ the normal stacking order — measured in the web app, where the tour card
4256
+ landed on top of this very surface and swallowed its clicks; that page went
4257
+ to z-[120] for the same reason and this has to clear it. */
4258
+ .fe-overlay {
4259
+ position: fixed;
4260
+ inset: 0;
4261
+ z-index: 130;
4262
+ display: flex;
4263
+ align-items: flex-start;
4264
+ justify-content: center;
4265
+ padding: 32px 16px;
4266
+ overflow: auto;
4267
+ background: rgba(13, 19, 27, 0.45);
4268
+ }
4269
+ .fe-overlay__card {
4270
+ width: min(880px, 100%);
4271
+ max-height: calc(100vh - 64px);
4272
+ overflow: auto;
4273
+ padding: 18px;
4274
+ border-radius: var(--fe-radius-lg);
4275
+ border: 1px solid var(--fe-border);
4276
+ background: var(--fe-bg);
4277
+ color: var(--fe-text);
4278
+ box-shadow: var(--fe-shadow);
4279
+ }
4280
+ .fe-overlay__head {
4281
+ display: flex;
4282
+ align-items: center;
4283
+ justify-content: space-between;
4284
+ gap: 8px;
4285
+ margin-bottom: 10px;
4286
+ }
4287
+ .fe-overlay__title {
4288
+ margin: 0;
4289
+ font-size: 15px;
4290
+ font-weight: 600;
4291
+ }
4292
+ .fe-overlay__close {
4293
+ border: 0;
4294
+ background: transparent;
4295
+ color: var(--fe-text-muted);
4296
+ font-size: 20px;
4297
+ line-height: 1;
4298
+ padding: 2px 6px;
4299
+ border-radius: var(--fe-radius-sm);
4300
+ cursor: pointer;
4301
+ }
4302
+ .fe-overlay__close:hover {
4303
+ background: var(--fe-bg-hover);
4304
+ color: var(--fe-text);
4305
+ }
4306
+ .fe-overlay__close:focus-visible {
4307
+ outline: 2px solid var(--fe-primary);
4308
+ outline-offset: 1px;
4309
+ }
4310
+ @media (max-width: 560px) {
4311
+ .fe-overlay {
4312
+ padding: 12px 8px;
4313
+ }
4314
+ .fe-overlay__card {
4315
+ padding: 12px;
4316
+ max-height: calc(100vh - 24px);
4317
+ }
4318
+ }
4319
+
4320
+ /* The full API-key form (TokensPanel `full`). */
4321
+ .fe-tokform {
4322
+ display: flex;
4323
+ flex-direction: column;
4324
+ gap: 10px;
4325
+ margin: 8px 0 4px;
4326
+ }
4327
+ .fe-tokform__scopes {
4328
+ display: flex;
4329
+ flex-wrap: wrap;
4330
+ align-items: center;
4331
+ gap: 12px;
4332
+ margin: 0;
4333
+ padding: 8px 10px;
4334
+ border: 1px solid var(--fe-border);
4335
+ border-radius: var(--fe-radius-sm);
4336
+ }
4337
+ .fe-tokform__legend {
4338
+ padding: 0 4px;
4339
+ font-size: 12px;
4340
+ color: var(--fe-text-muted);
4341
+ }
4342
+ .fe-tokform__scope {
4343
+ display: inline-flex;
4344
+ align-items: center;
4345
+ gap: 5px;
4346
+ font-size: 13px;
4347
+ font-family: var(--fe-font-mono);
4348
+ cursor: pointer;
4349
+ }
4350
+ .fe-tokform__field {
4351
+ display: flex;
4352
+ flex-direction: column;
4353
+ gap: 4px;
4354
+ min-width: 0;
4355
+ }
4356
+ .fe-tokform__field--narrow {
4357
+ flex: 0 0 180px;
4358
+ }
4359
+ .fe-tokform__label {
4360
+ font-size: 12px;
4361
+ color: var(--fe-text-muted);
4362
+ }
4363
+ .fe-tokform__row {
4364
+ display: flex;
4365
+ align-items: flex-end;
4366
+ gap: 10px;
4367
+ flex-wrap: wrap;
4368
+ }
4369
+ .fe-tokform__row .fe-s3keys__btn {
4370
+ margin-left: auto;
4371
+ }
4372
+ /* --- /gezinti:g1 --- */