mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-15 12:14:07 +00:00
feat(ui): scrollable resource tabs and railpack default
Add a shared resource-heading-tabs scroller with overflow chevrons, wire it into resource/server navbars, keep links menus outside overflow, and default new git apps to Railpack instead of Nixpacks.
This commit is contained in:
1 parent
9721fcdf11
commit
1bb54c0ca1
21 files changed
+452
-101
No files matched your search
@@ -1152,6 +1152,110 @@ body.terminal-is-fullscreen .terminal-fullscreen-shell [data-terminal-mobile-too
|
||||
height: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Kumo-style overflow controls for resource heading tabs.
|
||||
* Circular chevrons sit over gradient fades when more tabs are off-screen.
|
||||
*/
|
||||
.resource-heading-tabs-scroller {
|
||||
--resource-heading-tabs-fade: #f5f5f5; /* neutral-100, matches pill track */
|
||||
}
|
||||
|
||||
.dark .resource-heading-tabs-scroller {
|
||||
/* Matches dark:bg-white/[0.035] pill track + panel so fade blends */
|
||||
--resource-heading-tabs-fade: color-mix(in srgb, var(--color-panel, #0a0a0a) 96.5%, white);
|
||||
}
|
||||
|
||||
.resource-heading-tabs-control {
|
||||
position: absolute;
|
||||
inset-block: 0;
|
||||
z-index: 3;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 2.5rem;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
.resource-heading-tabs-control.is-visible {
|
||||
pointer-events: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.resource-heading-tabs-control.is-start {
|
||||
left: 0;
|
||||
justify-content: flex-start;
|
||||
background: linear-gradient(
|
||||
to right,
|
||||
var(--resource-heading-tabs-fade) 35%,
|
||||
color-mix(in srgb, var(--resource-heading-tabs-fade) 85%, transparent) 70%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.resource-heading-tabs-control.is-end {
|
||||
right: 0;
|
||||
justify-content: flex-end;
|
||||
background: linear-gradient(
|
||||
to left,
|
||||
var(--resource-heading-tabs-fade) 35%,
|
||||
color-mix(in srgb, var(--resource-heading-tabs-fade) 85%, transparent) 70%,
|
||||
transparent
|
||||
);
|
||||
}
|
||||
|
||||
.resource-heading-tabs-control-icon {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
margin-inline: 0.25rem;
|
||||
border-radius: 9999px;
|
||||
color: #737373;
|
||||
background: #ffffff;
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.06),
|
||||
0 0 0 1px rgba(0, 0, 0, 0.08);
|
||||
transition:
|
||||
background-color 0.15s ease,
|
||||
color 0.15s ease,
|
||||
box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.dark .resource-heading-tabs-control-icon {
|
||||
color: var(--color-fg-dim, #a3a3a3);
|
||||
background: var(--color-raised, #171717);
|
||||
box-shadow:
|
||||
0 1px 2px rgba(0, 0, 0, 0.35),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.resource-heading-tabs-control:hover .resource-heading-tabs-control-icon,
|
||||
.resource-heading-tabs-control:focus-visible .resource-heading-tabs-control-icon {
|
||||
color: #000000;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.dark .resource-heading-tabs-control:hover .resource-heading-tabs-control-icon,
|
||||
.dark .resource-heading-tabs-control:focus-visible .resource-heading-tabs-control-icon {
|
||||
color: var(--color-fg, #fafafa);
|
||||
background: var(--color-raised, #171717);
|
||||
}
|
||||
|
||||
.resource-heading-tabs-control:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.resource-heading-tabs-control:focus-visible .resource-heading-tabs-control-icon {
|
||||
box-shadow:
|
||||
0 0 0 2px color-mix(in srgb, var(--color-coollabs, #6b16ed) 45%, transparent),
|
||||
0 0 0 1px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
.resource-heading-menus,
|
||||
.resource-heading-actions {
|
||||
overflow: visible;
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="open" x-cloak x-transition.opacity.duration.120ms
|
||||
class="absolute left-0 z-[90] mt-2 min-w-60 max-w-96 max-h-80 overflow-y-auto rounded-lg border border-neutral-200 bg-white py-1.5 shadow-modal scrollbar dark:border-white/10 dark:bg-raised">
|
||||
class="absolute right-0 z-[90] mt-2 min-w-60 max-w-96 max-h-80 overflow-y-auto rounded-lg border border-neutral-200 bg-white py-1.5 shadow-modal scrollbar dark:border-white/10 dark:bg-raised md:left-0 md:right-auto">
|
||||
@if ($hasLinks)
|
||||
@if (data_get($application, 'gitBrancLocation'))
|
||||
<a target="_blank" class="{{ $linkItemClasses }}" href="{{ $application->gitBranchLocation }}">
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
:class="[typeof collapsed !== 'undefined' && collapsed ? 'lg:left-16' : 'lg:left-56']">
|
||||
<div
|
||||
class="resource-heading-navbar application-heading-actions flex w-full min-w-0 items-center justify-between gap-2 overflow-visible rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
|
||||
<div class="resource-heading-tabs flex min-w-0 items-center gap-0.5 overflow-x-auto">
|
||||
<x-resource-heading-tabs class="min-w-0">
|
||||
@foreach ($items as $item)
|
||||
<a @class([
|
||||
'app-tab shrink-0',
|
||||
@@ -35,7 +35,7 @@
|
||||
{{ $item['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-resource-heading-tabs>
|
||||
|
||||
<div class="resource-heading-actions flex shrink-0 items-center gap-0.5 border-l border-neutral-200 pl-1 dark:border-white/[0.08]">
|
||||
@isset($actions)
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
{{--
|
||||
Scrollable resource tab strip with Kumo-style overflow arrows.
|
||||
Left/right circular chevrons appear only when more tabs exist off-screen.
|
||||
On load / Livewire navigate, the active tab is scrolled into view so
|
||||
right-side items are not hidden after navigation on mobile.
|
||||
--}}
|
||||
@props([
|
||||
'listClass' => '',
|
||||
])
|
||||
|
||||
<div
|
||||
{{ $attributes->class('resource-heading-tabs-scroller relative isolate min-w-0') }}
|
||||
x-data="{
|
||||
canStart: false,
|
||||
canEnd: false,
|
||||
ro: null,
|
||||
mo: null,
|
||||
onNavigated: null,
|
||||
update() {
|
||||
const el = this.$refs.list
|
||||
if (!el) {
|
||||
return
|
||||
}
|
||||
const max = Math.max(0, el.scrollWidth - el.clientWidth)
|
||||
const left = Math.min(Math.max(0, el.scrollLeft), max)
|
||||
this.canStart = left > 1
|
||||
this.canEnd = (max - left) > 1
|
||||
},
|
||||
scrollByDir(dir) {
|
||||
const el = this.$refs.list
|
||||
if (!el) {
|
||||
return
|
||||
}
|
||||
const amount = Math.max(80, Math.floor(el.clientWidth * 0.7))
|
||||
el.scrollBy({ left: dir === 'start' ? -amount : amount, behavior: 'smooth' })
|
||||
},
|
||||
findActiveTab() {
|
||||
const el = this.$refs.list
|
||||
if (!el) {
|
||||
return null
|
||||
}
|
||||
|
||||
return Array.from(el.children).find((child) => {
|
||||
if (!(child instanceof HTMLElement)) {
|
||||
return false
|
||||
}
|
||||
if (child.getAttribute('aria-current') === 'page') {
|
||||
return true
|
||||
}
|
||||
if (child.hasAttribute('data-active') || child.dataset.active === 'true') {
|
||||
return true
|
||||
}
|
||||
// Active resource tabs use coollabs/warning highlight classes.
|
||||
const cls = typeof child.className === 'string' ? child.className : ''
|
||||
return cls.includes('ring-coollabs')
|
||||
|| cls.includes('bg-coollabs')
|
||||
|| cls.includes('ring-warning')
|
||||
|| cls.includes('bg-warning')
|
||||
}) ?? null
|
||||
},
|
||||
scrollActiveIntoView() {
|
||||
const list = this.$refs.list
|
||||
const active = this.findActiveTab()
|
||||
if (!list || !active) {
|
||||
this.update()
|
||||
return
|
||||
}
|
||||
|
||||
// Center the active tab so right-edge items are fully visible after navigate.
|
||||
const listWidth = list.clientWidth
|
||||
const maxScroll = Math.max(0, list.scrollWidth - listWidth)
|
||||
const activeCenter = active.offsetLeft + (active.offsetWidth / 2)
|
||||
const target = Math.min(maxScroll, Math.max(0, activeCenter - (listWidth / 2)))
|
||||
|
||||
list.scrollTo({ left: target, behavior: 'auto' })
|
||||
this.update()
|
||||
},
|
||||
scheduleScrollActiveIntoView() {
|
||||
// Layout may not be final on first paint (fonts, Livewire morph, flex).
|
||||
this.$nextTick(() => {
|
||||
this.scrollActiveIntoView()
|
||||
requestAnimationFrame(() => {
|
||||
this.scrollActiveIntoView()
|
||||
requestAnimationFrame(() => this.scrollActiveIntoView())
|
||||
})
|
||||
})
|
||||
},
|
||||
init() {
|
||||
this.onNavigated = () => this.scheduleScrollActiveIntoView()
|
||||
document.addEventListener('livewire:navigated', this.onNavigated)
|
||||
|
||||
this.$nextTick(() => {
|
||||
this.scheduleScrollActiveIntoView()
|
||||
const el = this.$refs.list
|
||||
if (!el || typeof ResizeObserver === 'undefined') {
|
||||
return
|
||||
}
|
||||
this.ro = new ResizeObserver(() => {
|
||||
this.update()
|
||||
})
|
||||
this.ro.observe(el)
|
||||
if (typeof MutationObserver !== 'undefined') {
|
||||
this.mo = new MutationObserver(() => {
|
||||
this.update()
|
||||
})
|
||||
this.mo.observe(el, { childList: true, subtree: true, characterData: true, attributes: true, attributeFilter: ['class', 'aria-current', 'data-active'] })
|
||||
}
|
||||
})
|
||||
},
|
||||
destroy() {
|
||||
this.ro?.disconnect()
|
||||
this.mo?.disconnect()
|
||||
if (this.onNavigated) {
|
||||
document.removeEventListener('livewire:navigated', this.onNavigated)
|
||||
}
|
||||
},
|
||||
}"
|
||||
x-init="init()"
|
||||
@resize.window.debounce.50ms="update()"
|
||||
>
|
||||
<div
|
||||
x-ref="list"
|
||||
@scroll.passive="update()"
|
||||
@class([
|
||||
'resource-heading-tabs flex w-full min-w-0 items-center gap-0.5 overflow-x-auto',
|
||||
$listClass,
|
||||
])
|
||||
:data-overflow-start="canStart ? '' : null"
|
||||
:data-overflow-end="canEnd ? '' : null"
|
||||
>
|
||||
{{ $slot }}
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="resource-heading-tabs-control is-start"
|
||||
aria-label="Scroll tabs left"
|
||||
:aria-hidden="(!canStart).toString()"
|
||||
:tabindex="canStart ? 0 : -1"
|
||||
:class="canStart ? 'is-visible' : ''"
|
||||
@click="scrollByDir('start')"
|
||||
>
|
||||
<span class="resource-heading-tabs-control-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="size-3.5">
|
||||
<path d="M9.25 4.25L5.75 8L9.25 11.75" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
class="resource-heading-tabs-control is-end"
|
||||
aria-label="Scroll tabs right"
|
||||
:aria-hidden="(!canEnd).toString()"
|
||||
:tabindex="canEnd ? 0 : -1"
|
||||
:class="canEnd ? 'is-visible' : ''"
|
||||
@click="scrollByDir('end')"
|
||||
>
|
||||
<span class="resource-heading-tabs-control-icon" aria-hidden="true">
|
||||
<svg viewBox="0 0 16 16" fill="none" class="size-3.5">
|
||||
<path d="M6.75 4.25L10.25 8L6.75 11.75" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round" />
|
||||
</svg>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
@@ -14,7 +14,7 @@
|
||||
</svg>
|
||||
</button>
|
||||
<div x-show="open" x-cloak x-transition.opacity.duration.120ms
|
||||
class="absolute left-0 z-[90] mt-2 min-w-60 max-w-96 max-h-80 overflow-y-auto rounded-lg border border-neutral-200 bg-white py-1.5 shadow-modal scrollbar dark:border-white/10 dark:bg-raised">
|
||||
class="absolute right-0 z-[90] mt-2 min-w-60 max-w-96 max-h-80 overflow-y-auto rounded-lg border border-neutral-200 bg-white py-1.5 shadow-modal scrollbar dark:border-white/10 dark:bg-raised md:left-0 md:right-auto">
|
||||
@foreach ($links as $link)
|
||||
<a class="{{ $linkItemClasses }}" target="_blank" href="{{ $link }}">
|
||||
<x-external-link class="size-3.5 shrink-0" />
|
||||
|
||||
@@ -81,8 +81,8 @@
|
||||
<div class="application-build-pack-options mb-5 border-b border-neutral-200 pb-5 dark:border-white/[0.07]">
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<x-forms.listbox id="buildPack" label="Build strategy" live :options="[
|
||||
['value' => 'railpack', 'label' => 'Railpack'],
|
||||
['value' => 'nixpacks', 'label' => 'Nixpacks'],
|
||||
['value' => 'railpack', 'label' => 'Railpack (beta)'],
|
||||
['value' => 'static', 'label' => 'Static'],
|
||||
['value' => 'dockerfile', 'label' => 'Dockerfile'],
|
||||
['value' => 'dockercompose', 'label' => 'Compose'],
|
||||
|
||||
@@ -272,21 +272,27 @@
|
||||
</div>
|
||||
@endif
|
||||
<div
|
||||
class="flex min-w-0 items-center gap-0.5 overflow-x-auto rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
|
||||
@foreach ($applicationMenuItems as $menuItem)
|
||||
@php
|
||||
$isMobileApplicationItemActive = $menuItem['active']
|
||||
|| ($menuItem['label'] === 'Settings' && $activeConfigurationMenuItem);
|
||||
@endphp
|
||||
<a @class([
|
||||
'app-tab shrink-0',
|
||||
'bg-coollabs/10 text-coollabs ring-1 ring-coollabs/25 dark:bg-warning/15 dark:text-warning dark:ring-warning/25' => $isMobileApplicationItemActive,
|
||||
])
|
||||
@if ($menuItem['navigate'] ?? true) {{ wireNavigate() }} @endif
|
||||
href="{{ route($menuItem['route'], $parameters) }}">
|
||||
{{ $menuItem['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
class="flex min-w-0 items-center gap-0.5 rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
|
||||
{{-- Tabs may scroll; keep Links outside overflow so the dropdown never creates a scrollbar. --}}
|
||||
<x-resource-heading-tabs class="min-w-0 flex-1">
|
||||
@foreach ($applicationMenuItems as $menuItem)
|
||||
@php
|
||||
$isMobileApplicationItemActive = $menuItem['active']
|
||||
|| ($menuItem['label'] === 'Settings' && $activeConfigurationMenuItem);
|
||||
@endphp
|
||||
<a @class([
|
||||
'app-tab shrink-0',
|
||||
'bg-coollabs/10 text-coollabs ring-1 ring-coollabs/25 dark:bg-warning/15 dark:text-warning dark:ring-warning/25' => $isMobileApplicationItemActive,
|
||||
])
|
||||
@if ($menuItem['navigate'] ?? true) {{ wireNavigate() }} @endif
|
||||
href="{{ route($menuItem['route'], $parameters) }}">
|
||||
{{ $menuItem['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
</x-resource-heading-tabs>
|
||||
<div class="resource-heading-menus shrink-0">
|
||||
<x-applications.links :application="$application" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden" aria-hidden="true">
|
||||
<x-modal-confirmation title="Confirm Application Stopping?" buttonTitle="Stop"
|
||||
@@ -318,7 +324,7 @@
|
||||
class="resource-heading-navbar application-heading-actions flex w-full min-w-0 items-center justify-between gap-2 overflow-visible rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
|
||||
<div class="application-primary-tabs flex min-w-0 items-center gap-0.5">
|
||||
{{-- Tabs alone may scroll; keep Links outside overflow so the dropdown never creates a scrollbar. --}}
|
||||
<div class="resource-heading-tabs flex min-w-0 items-center gap-0.5 overflow-x-auto">
|
||||
<x-resource-heading-tabs class="min-w-0">
|
||||
@foreach ($applicationMenuItems as $menuItem)
|
||||
@php
|
||||
$isApplicationMenuItemActive = $menuItem['active']
|
||||
@@ -338,7 +344,7 @@
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-resource-heading-tabs>
|
||||
<div class="resource-heading-menus shrink-0">
|
||||
<x-applications.links :application="$application" />
|
||||
</div>
|
||||
|
||||
@@ -150,17 +150,19 @@
|
||||
@endif
|
||||
|
||||
<div
|
||||
class="flex min-w-0 items-center gap-0.5 overflow-x-auto rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
|
||||
@foreach ($databasePageItems as $menuItem)
|
||||
<a @class([
|
||||
'app-tab shrink-0',
|
||||
'bg-coollabs/10 text-coollabs ring-1 ring-coollabs/25 dark:bg-warning/15 dark:text-warning dark:ring-warning/25' => $menuItem['active'],
|
||||
])
|
||||
@if ($menuItem['navigate'] ?? true) {{ wireNavigate() }} @endif
|
||||
href="{{ route($menuItem['route'], $parameters) }}">
|
||||
{{ $menuItem['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
class="flex min-w-0 items-center gap-0.5 rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
|
||||
<x-resource-heading-tabs class="min-w-0 flex-1">
|
||||
@foreach ($databasePageItems as $menuItem)
|
||||
<a @class([
|
||||
'app-tab shrink-0',
|
||||
'bg-coollabs/10 text-coollabs ring-1 ring-coollabs/25 dark:bg-warning/15 dark:text-warning dark:ring-warning/25' => $menuItem['active'],
|
||||
])
|
||||
@if ($menuItem['navigate'] ?? true) {{ wireNavigate() }} @endif
|
||||
href="{{ route($menuItem['route'], $parameters) }}">
|
||||
{{ $menuItem['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
</x-resource-heading-tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -168,7 +170,7 @@
|
||||
:class="[typeof collapsed !== 'undefined' && collapsed ? 'lg:left-16' : 'lg:left-56']">
|
||||
<div
|
||||
class="resource-heading-navbar application-heading-actions flex w-full min-w-0 items-center justify-between gap-2 overflow-visible rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
|
||||
<div class="resource-heading-tabs flex min-w-0 items-center gap-0.5 overflow-x-auto">
|
||||
<x-resource-heading-tabs class="min-w-0">
|
||||
@foreach ($databasePageItems as $menuItem)
|
||||
<a wire:key="database-primary-nav-{{ str($menuItem['label'])->slug() }}"
|
||||
@class([
|
||||
@@ -180,7 +182,7 @@
|
||||
{{ $menuItem['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-resource-heading-tabs>
|
||||
|
||||
<div class="resource-heading-actions flex shrink-0 items-center gap-0.5 border-l border-neutral-200 pl-1 dark:border-white/[0.08]">
|
||||
@if ($database->destination->server->isFunctional())
|
||||
|
||||
@@ -56,8 +56,8 @@
|
||||
<div class="grid gap-4 sm:grid-cols-2">
|
||||
<x-forms.input id="branch" required label="Branch" />
|
||||
<x-forms.listbox id="build_pack" label="Build pack" required live :options="[
|
||||
['value' => 'railpack', 'label' => 'Railpack'],
|
||||
['value' => 'nixpacks', 'label' => 'Nixpacks'],
|
||||
['value' => 'railpack', 'label' => 'Railpack (Beta)'],
|
||||
['value' => 'static', 'label' => 'Static'],
|
||||
['value' => 'dockerfile', 'label' => 'Dockerfile'],
|
||||
['value' => 'dockercompose', 'label' => 'Docker Compose'],
|
||||
|
||||
@@ -109,8 +109,8 @@
|
||||
'label' => data_get($branch, 'name'),
|
||||
])->values()->all()" />
|
||||
<x-forms.listbox id="build_pack" label="Build pack" required live :options="[
|
||||
['value' => 'railpack', 'label' => 'Railpack'],
|
||||
['value' => 'nixpacks', 'label' => 'Nixpacks'],
|
||||
['value' => 'railpack', 'label' => 'Railpack (Beta)'],
|
||||
['value' => 'static', 'label' => 'Static'],
|
||||
['value' => 'dockerfile', 'label' => 'Dockerfile'],
|
||||
['value' => 'dockercompose', 'label' => 'Docker Compose'],
|
||||
|
||||
@@ -108,8 +108,8 @@
|
||||
'label' => data_get($branch, 'name'),
|
||||
])->values()->all()" />
|
||||
<x-forms.listbox id="build_pack" label="Build pack" required live :options="[
|
||||
['value' => 'railpack', 'label' => 'Railpack'],
|
||||
['value' => 'nixpacks', 'label' => 'Nixpacks'],
|
||||
['value' => 'railpack', 'label' => 'Railpack (Beta)'],
|
||||
['value' => 'static', 'label' => 'Static'],
|
||||
['value' => 'dockerfile', 'label' => 'Dockerfile'],
|
||||
['value' => 'dockercompose', 'label' => 'Docker Compose'],
|
||||
|
||||
@@ -61,8 +61,8 @@
|
||||
:disabled="$git_source !== 'other'"
|
||||
helper="You can choose another branch after the application is created." />
|
||||
<x-forms.listbox id="build_pack" label="Build pack" required live :options="[
|
||||
['value' => 'railpack', 'label' => 'Railpack'],
|
||||
['value' => 'nixpacks', 'label' => 'Nixpacks'],
|
||||
['value' => 'railpack', 'label' => 'Railpack (Beta)'],
|
||||
['value' => 'static', 'label' => 'Static'],
|
||||
['value' => 'dockerfile', 'label' => 'Dockerfile'],
|
||||
['value' => 'dockercompose', 'label' => 'Docker Compose'],
|
||||
|
||||
@@ -174,17 +174,23 @@
|
||||
@endif
|
||||
|
||||
<div
|
||||
class="flex min-w-0 items-center gap-0.5 overflow-x-auto rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
|
||||
@foreach ($servicePageItems as $menuItem)
|
||||
<a @class([
|
||||
'app-tab shrink-0',
|
||||
'bg-coollabs/10 text-coollabs ring-1 ring-coollabs/25 dark:bg-warning/15 dark:text-warning dark:ring-warning/25' => $menuItem['active'],
|
||||
])
|
||||
@if ($menuItem['navigate'] ?? true) {{ wireNavigate() }} @endif
|
||||
href="{{ route($menuItem['route'], $parameters) }}">
|
||||
{{ $menuItem['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
class="flex min-w-0 items-center gap-0.5 rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
|
||||
{{-- Tabs may scroll; keep Links outside overflow so the dropdown never creates a scrollbar. --}}
|
||||
<x-resource-heading-tabs class="min-w-0 flex-1">
|
||||
@foreach ($servicePageItems as $menuItem)
|
||||
<a @class([
|
||||
'app-tab shrink-0',
|
||||
'bg-coollabs/10 text-coollabs ring-1 ring-coollabs/25 dark:bg-warning/15 dark:text-warning dark:ring-warning/25' => $menuItem['active'],
|
||||
])
|
||||
@if ($menuItem['navigate'] ?? true) {{ wireNavigate() }} @endif
|
||||
href="{{ route($menuItem['route'], $parameters) }}">
|
||||
{{ $menuItem['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
</x-resource-heading-tabs>
|
||||
<div class="resource-heading-menus shrink-0">
|
||||
<x-services.links :service="$service" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -193,7 +199,7 @@
|
||||
<div
|
||||
class="resource-heading-navbar application-heading-actions flex w-full min-w-0 items-center justify-between gap-2 overflow-visible rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
|
||||
<div class="flex min-w-0 items-center gap-0.5">
|
||||
<div class="resource-heading-tabs flex min-w-0 items-center gap-0.5 overflow-x-auto">
|
||||
<x-resource-heading-tabs class="min-w-0">
|
||||
@foreach ($servicePageItems as $menuItem)
|
||||
<a wire:key="service-primary-nav-{{ str($menuItem['label'])->slug() }}"
|
||||
@class([
|
||||
@@ -205,7 +211,7 @@
|
||||
{{ $menuItem['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-resource-heading-tabs>
|
||||
<div class="resource-heading-menus shrink-0">
|
||||
<x-services.links :service="$service" />
|
||||
</div>
|
||||
|
||||
@@ -283,17 +283,19 @@
|
||||
@endif
|
||||
|
||||
<div
|
||||
class="flex min-w-0 items-center gap-0.5 overflow-x-auto rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
|
||||
@foreach ($serverMenuItems as $menuItem)
|
||||
<a @class([
|
||||
'app-tab shrink-0',
|
||||
'bg-coollabs/10 text-coollabs ring-1 ring-coollabs/25 dark:bg-warning/15 dark:text-warning dark:ring-warning/25' => $menuItem['active'],
|
||||
])
|
||||
@if ($menuItem['navigate'] ?? true) {{ wireNavigate() }} @endif
|
||||
href="{{ route($menuItem['route'], $serverRouteParameters) }}">
|
||||
{{ $menuItem['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
class="flex min-w-0 items-center gap-0.5 rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
|
||||
<x-resource-heading-tabs class="min-w-0 flex-1">
|
||||
@foreach ($serverMenuItems as $menuItem)
|
||||
<a @class([
|
||||
'app-tab shrink-0',
|
||||
'bg-coollabs/10 text-coollabs ring-1 ring-coollabs/25 dark:bg-warning/15 dark:text-warning dark:ring-warning/25' => $menuItem['active'],
|
||||
])
|
||||
@if ($menuItem['navigate'] ?? true) {{ wireNavigate() }} @endif
|
||||
href="{{ route($menuItem['route'], $serverRouteParameters) }}">
|
||||
{{ $menuItem['label'] }}
|
||||
</a>
|
||||
@endforeach
|
||||
</x-resource-heading-tabs>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -301,8 +303,7 @@
|
||||
:class="[typeof collapsed !== 'undefined' && collapsed ? 'lg:left-16' : 'lg:left-56']">
|
||||
<div
|
||||
class="resource-heading-navbar application-heading-actions flex w-full min-w-0 items-center justify-between gap-2 overflow-visible rounded-[10px] border border-neutral-200 bg-neutral-100 p-1 dark:border-white/[0.07] dark:bg-white/[0.035]">
|
||||
<div
|
||||
class="application-primary-tabs resource-heading-tabs flex min-w-0 items-center gap-0.5 overflow-x-auto">
|
||||
<x-resource-heading-tabs class="application-primary-tabs min-w-0">
|
||||
@foreach ($serverMenuItems as $menuItem)
|
||||
<a wire:key="server-primary-nav-{{ str($menuItem['label'])->slug() }}"
|
||||
@class([
|
||||
@@ -318,7 +319,7 @@
|
||||
@endif
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-resource-heading-tabs>
|
||||
|
||||
@if ($server->proxySet())
|
||||
@can('manageProxy', $server)
|
||||
|
||||
Reference in new issue
Block a user