feat(10-03): tabs, toggles, relation fields, form lifecycle and settings pages
- FormTabs groups fields by tab (untabbed fields in the default tab) as a
segmented tablist; a tab holding invalid fields after a 422 shows a
count badge and the form switches to the first invalid field
- switch, checkbox and relation join the renderer registry: toggle cards
(role=switch, 20px checkbox) keep a numeric value numeric; relation
fields are read-only labels from meta.labels, a searchable single
select over fields/{field}/options with emptyOption first, or ordered
removable chips with an appending search (300 ms debounce, 20 per
page, more on scroll or the more action)
- FormView gets the back button, record title and update subtitle, a
sticky footer (Usuń with deleteConfirm then DELETE, Anuluj, Zapisz i
zamknij to the mapped redirectClose, Zapisz), and asks before leaving a
dirty form on any route change plus a beforeunload guard
- Settings: the rail pins Ustawienia to the bottom when /settings is
non-empty; /settings lists pages by category; /settings/:code renders
the settings schema through FormGrid and the registry, PUTs the values
and maps a 422 like the record form
- New backend::lang keys (form.more_options, tab_default, discard,
settings.back); form and settings smoke tests; boardwalk/dist rebuilt
This commit is contained in:
@@ -1,15 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { RouterLink, useRoute } from 'vue-router'
|
||||
import { Sun } from '@lucide/vue'
|
||||
import { Settings, Sun } from '@lucide/vue'
|
||||
import { t } from '../../app/i18n'
|
||||
import { iconFor } from '../../app/icons'
|
||||
import { activeEntry, firstControllerPath, railEntries } from '../../state/useNavigation'
|
||||
import { hasSettings } from '../../state/useSettings'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const active = computed(() => activeEntry(String(route.params.vendor ?? ''), String(route.params.plugin ?? '')))
|
||||
|
||||
// Ustawienia (D-21) is pinned to the bottom and shown only when the admin may
|
||||
// manage at least one settings page.
|
||||
const inSettings = computed(() => route.path === '/settings' || route.path.startsWith('/settings/'))
|
||||
|
||||
const items = computed(() =>
|
||||
railEntries.value.map((entry) => ({
|
||||
entry,
|
||||
@@ -39,5 +44,17 @@ const items = computed(() =>
|
||||
<component :is="item.icon" :size="20" aria-hidden="true" />
|
||||
<span>{{ item.entry.label }}</span>
|
||||
</RouterLink>
|
||||
<span class="flex-1" aria-hidden="true" />
|
||||
<RouterLink
|
||||
v-if="hasSettings"
|
||||
to="/settings"
|
||||
data-rail-settings
|
||||
:aria-current="inSettings ? 'page' : undefined"
|
||||
:class="inSettings ? 'bg-accent-soft font-bold text-accent' : 'text-side-text hover:bg-side-hover hover:text-white'"
|
||||
class="flex w-[68px] flex-col items-center gap-1 rounded-inner px-1.5 py-2 text-center text-[11px] leading-[1.2] tracking-[-0.01em] no-underline"
|
||||
>
|
||||
<Settings :size="20" aria-hidden="true" />
|
||||
<span>{{ t('backend::lang.nav.settings') }}</span>
|
||||
</RouterLink>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user