@brftech/filex-core 0.29.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"
package/src/locales/en.ts CHANGED
@@ -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
@@ -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 --- */
@@ -257,6 +257,74 @@ export interface ExplorerConfig {
257
257
  /** Whether the info panel toggle is visible. */
258
258
  showInfoPanel?: boolean;
259
259
 
260
+ /**
261
+ * Which set of chrome the explorer presents. A PRESET, not a feature switch:
262
+ * nothing is removed from the code, and every capability stays reachable —
263
+ * `'simple'` only changes what is on screen by default.
264
+ *
265
+ * 'standard' (default) — everything: tab strip, split pane, all three view
266
+ * modes. The tool the explorer has always been.
267
+ * 'simple' — one pane, one folder, list/grid only. The
268
+ * navigation panel starts expanded, the tab strip
269
+ * and split pane are off, the gallery view mode and
270
+ * the host's "How to connect" surface are hidden.
271
+ *
272
+ * Why it exists (GitHub #14): the reporter's users are not in IT and read
273
+ * split panes, tabs and mount instructions as a file manager they would have
274
+ * to relearn. The answer was NOT a second UI — one explorer, configured, so
275
+ * a fix lands in one place for every surface that mounts this package.
276
+ *
277
+ * ⚠ It does not gate the navigation panel. The panel ships in both profiles,
278
+ * for administrators too; only its default expanded/collapsed state and the
279
+ * rest of the chrome differ. A viewer's own collapse choice, once made,
280
+ * outranks the profile — it is a per-viewer preference, not a policy.
281
+ */
282
+ uiProfile?: 'standard' | 'simple';
283
+
284
+ /**
285
+ * Render the navigation panel (Upload · Recent / Starred / Shared with me /
286
+ * Trash · the storage list). Collapsible to an icon rail by the viewer, whose
287
+ * choice is remembered per browser.
288
+ *
289
+ * Default: ON — everywhere, including the desktop app and every embed. This
290
+ * package is the reason those are one product; a panel that appeared in the
291
+ * web app and nowhere else would turn it back into three (0.16.0 did exactly
292
+ * that with `tabStrip` and it had to be undone).
293
+ *
294
+ * ⚠ ONE exception, and it is about `rootPath`, not about who is embedding: a
295
+ * confined embed has no storage list to show, and its views would list files
296
+ * from OUTSIDE the folder the embed was confined to. So `rootPath` flips the
297
+ * default to off. Setting `sideNav: true` alongside `rootPath` still wins —
298
+ * the panel is then yours, along with what its views will show.
299
+ */
300
+ sideNav?: boolean;
301
+
302
+ /**
303
+ * Show the navigation panel's **Connections** entries — "How to connect"
304
+ * (the storage list and the per-protocol guides: WebDAV · SFTP · FTPS · S3 ·
305
+ * NFS · `filex mount`) and **API keys** (mint and revoke the tokens three of
306
+ * those protocols sign in with).
307
+ *
308
+ * Default: on, EXCEPT under `uiProfile: 'simple'`, where it is off. Those two
309
+ * defaults answer two different people: #14's reporter called mount
310
+ * instructions power-user noise in front of users who are not in IT, and an
311
+ * embedder's own tenant may still legitimately need an S3 key. Whichever one
312
+ * you are, say so explicitly and the default stops mattering.
313
+ *
314
+ * ⚠ Not gated on role, ever. The backend decides what a caller may see —
315
+ * ConnectionsPanel renders what the API returns, and `/api/tokens` caps every
316
+ * scope against the caller's own role and grants. A UI-side role check here
317
+ * would only hide the surface from the accounts that need it most; that is
318
+ * the bug this became: for a year the sole place to mint the token the FTPS
319
+ * guide told you to use was the admin panel.
320
+ *
321
+ * ⚠ The entries live in the panel, so `sideNav: false` takes them with it.
322
+ * Hosts that want the surface without the panel mount `<filex-connections>`
323
+ * (or `ConnectionsPanel`) on a page of their own.
324
+ */
325
+ connections?: boolean;
326
+
327
+
260
328
  /** Default view. */
261
329
  viewMode?: 'list' | 'grid';
262
330