@falcondev-oss/nuxt-layers-base 0.43.1 → 0.43.3

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.
@@ -52,6 +52,8 @@ export default defineSetupComponent(
52
52
  }
53
53
  slots: {
54
54
  'default': () => VNode[]
55
+ 'navbar-left'?: () => VNode[]
56
+ 'navbar-leading'?: () => VNode[]
55
57
  'navbar-title'?: () => VNode[]
56
58
  'navbar-trailing'?: () => VNode[]
57
59
  'navbar-actions'?: () => VNode[]
@@ -62,6 +64,8 @@ export default defineSetupComponent(
62
64
  props: ['panel', 'navbar', 'tabs', 'tools', 'toolbarUi'],
63
65
  emits: [],
64
66
  setup: (props, { slots }) => {
67
+ const appConfig = useAppConfig()
68
+
65
69
  // rendered manually in the `left` slot below
66
70
  const omitNavbarSlots = [
67
71
  'navbar-left',
@@ -141,13 +145,33 @@ export default defineSetupComponent(
141
145
  (props.tools?.left || toolsCrowded.value),
142
146
  )
143
147
 
148
+ // This `h1` stands in for the navbar's own, which never renders once `left` is taken
149
+ // over — so everything upstream would have layered onto that slot has to be layered on
150
+ // by hand: the theme's own classes come from `app.config`, the per-instance ones from
151
+ // `ui.title`, and either may be a replacer function rather than classes to merge.
152
+ const navbarTitleClass = computed(() =>
153
+ mergeSlotClass(
154
+ props.navbar?.ui?.title,
155
+ mergeSlotClass(
156
+ appConfig.ui?.dashboardNavbar?.slots?.title,
157
+ 'text-highlighted font-semibold whitespace-nowrap',
158
+ )(''),
159
+ )(''),
160
+ )
161
+
144
162
  const navbarUi = computed<DashboardNavbarProps['ui']>(() => ({
145
163
  ...props.navbar?.ui,
146
164
  toggle: mergeSlotClass(props.navbar?.ui?.toggle, '-ml-1'),
147
- // Sized by its own content up to half the row, never squeezed by the actions: the edge
148
- // they measure their room from has to stay put, or an action that gives way frees space
149
- // for the title to grow into and comes straight back.
150
- left: mergeSlotClass(props.navbar?.ui?.left, 'max-w-1/2 shrink-0'),
165
+ // Exactly as wide as the title and breadcrumb need neither truncates, and no room
166
+ // is held back from the actions.
167
+ left: mergeSlotClass(
168
+ props.navbar?.ui?.left,
169
+ // A breadcrumb wraps, so this column may give way once the row runs short — down to
170
+ // the widest line it still has to show whole. With nothing to reflow, `shrink-0`
171
+ // instead keeps that edge put: an action giving way would otherwise free space for
172
+ // the title to grow into and come straight back.
173
+ props.navbar?.breadcrumb ? 'min-w-0' : 'shrink-0',
174
+ ),
151
175
  ...(props.navbar?.breadcrumb && {
152
176
  root: mergeSlotClass(props.navbar.ui?.root, 'h-auto min-h-(--ui-header-height) py-2'),
153
177
  }),
@@ -168,38 +192,48 @@ export default defineSetupComponent(
168
192
  title={props.navbar.title}
169
193
  v-slots={vSlots(UDashboardNavbar, {
170
194
  left: () => [
195
+ // the toggle stays outside `left` upstream, so it survives a
196
+ // `navbar-left` that replaces everything beside it
171
197
  ...(props.navbar?.sidebarToggle
172
198
  ? [<UDashboardSidebarCollapse class="-ml-1" />]
173
199
  : []),
174
200
 
175
- <div class="flex min-w-0 flex-col items-start gap-0.5">
176
- {/* title and breadcrumb both count towards the column's
177
- width it is as wide as the wider of the two, and each
178
- truncates on its own once the column hits `max-w-1/2` */}
179
- <div class="flex max-w-full min-w-0 items-center gap-1.5">
180
- <h1 class="text-highlighted truncate font-semibold">
181
- {slots['navbar-title']?.() ?? props.navbar?.title}
182
- </h1>
183
- {slots['navbar-trailing']?.()}
184
- </div>
201
+ ...(slots['navbar-left']?.() ?? [
202
+ <div class="flex min-w-0 flex-col items-start gap-0.5">
203
+ {/* title and breadcrumb size the column between them, so
204
+ neither is ever cut off */}
205
+ <div class="flex items-center gap-1.5">
206
+ {slots['navbar-leading']?.()}
207
+ <h1 data-slot="title" class={navbarTitleClass.value}>
208
+ {slots['navbar-title']?.() ?? props.navbar?.title}
209
+ </h1>
210
+ {slots['navbar-trailing']?.()}
211
+ </div>
185
212
 
186
- {props.navbar?.breadcrumb ? (
187
- <UBreadcrumb
188
- items={props.navbar.breadcrumb}
189
- ui={{
190
- ...props.navbar.breadcrumbUi,
191
- link: mergeSlotClass(
192
- props.navbar.breadcrumbUi?.link,
193
- 'text-xs',
194
- ),
195
- separatorIcon: mergeSlotClass(
196
- props.navbar.breadcrumbUi?.separatorIcon,
197
- 'size-3.5',
198
- ),
199
- }}
200
- />
201
- ) : null}
202
- </div>,
213
+ {props.navbar?.breadcrumb ? (
214
+ <UBreadcrumb
215
+ items={props.navbar.breadcrumb}
216
+ ui={{
217
+ ...props.navbar.breadcrumbUi,
218
+ link: mergeSlotClass(
219
+ props.navbar.breadcrumbUi?.link,
220
+ 'text-xs',
221
+ ),
222
+ separatorIcon: mergeSlotClass(
223
+ props.navbar.breadcrumbUi?.separatorIcon,
224
+ 'size-3.5',
225
+ ),
226
+ // onto further lines once the row runs short, rather
227
+ // than truncating the trail
228
+ list: mergeSlotClass(
229
+ props.navbar.breadcrumbUi?.list,
230
+ 'flex-wrap',
231
+ ),
232
+ }}
233
+ />
234
+ ) : null}
235
+ </div>,
236
+ ]),
203
237
  ],
204
238
  right: (slotProps) => [
205
239
  <>{slots['navbar-right']?.(slotProps)}</>,
@@ -43,20 +43,17 @@ export default defineSetupComponent(
43
43
  // sections draw their own top/left border shifted by -1px, so the outer ones get clipped
44
44
  <div
45
45
  class={[
46
- 'ring-default bg-elevated flex flex-wrap justify-end overflow-hidden rounded-t-lg shadow-[inset_0_2px_3px_-2px_rgb(0_0_0/0.06),inset_2px_0_3px_-2px_rgb(0_0_0/0.06),inset_-2px_0_3px_-2px_rgb(0_0_0/0.06)] ring',
46
+ 'ring-default bg-elevated flex flex-wrap overflow-hidden rounded-t-lg shadow-[inset_0_2px_3px_-2px_rgb(0_0_0/0.06),inset_2px_0_3px_-2px_rgb(0_0_0/0.06),inset_-2px_0_3px_-2px_rgb(0_0_0/0.06)] ring',
47
47
  slots.default ? '-mb-2 pb-2' : 'rounded-b-lg',
48
48
  ]}
49
49
  >
50
50
  {leadingSections}
51
51
 
52
- {/* every section hugs its content, so the spare space collects here instead of
53
- stretching the last leading section */}
54
- <div class="flex-1" />
55
-
56
52
  {endSections.length > 0 ? (
57
53
  // one flex item, so the end sections wrap onto their own line as a group
58
- // instead of splitting up — the leading sections give way first
59
- <div class="flex flex-wrap justify-end">{endSections}</div>
54
+ // instead of splitting up — the leading sections give way first;
55
+ // `ml-auto` eats the spare space, so leading sections stay left on every line
56
+ <div class="ml-auto flex flex-wrap justify-end">{endSections}</div>
60
57
  ) : null}
61
58
  </div>
62
59
  ) : null}
@@ -27,7 +27,10 @@ export default defineSetupComponent(
27
27
  <div
28
28
  data-end={props.end || undefined}
29
29
  class={cnMerge(
30
- 'border-default -mt-px -ml-px flex min-w-fit flex-none flex-col gap-1.5 border-l px-3 py-2',
30
+ // no `flex-none`/`min-w-fit`: a section still hugs its content (grow 0) and still wraps onto
31
+ // its own ribbon line, but one too wide for the whole ribbon shrinks instead of
32
+ // overflowing, so its content can wrap
33
+ 'border-default -mt-px -ml-px flex flex-col gap-1.5 border-l px-3 py-2',
31
34
  // the row separator spans the full ribbon width (clipped by its overflow-hidden), so a
32
35
  // partially filled wrapped line still gets an unbroken line above it
33
36
  'before:border-default relative before:absolute before:inset-x-[-100vw] before:top-0 before:border-t',
package/app/utils/ui.ts CHANGED
@@ -1,12 +1,13 @@
1
- import type { SlotClass, SlotClassReplacer } from '@nuxt/ui'
1
+ import type { SlotClass } from '@nuxt/ui'
2
2
  import type { ClassValue } from 'tailwind-variants'
3
3
  import { cnMerge } from 'tailwind-variants'
4
4
 
5
- export function mergeSlotClass(ui: SlotClass, extend: ClassValue): SlotClassReplacer {
5
+ /** Always resolves to a class string, so the result also stands on its own as a `class`. */
6
+ export function mergeSlotClass(ui: SlotClass, extend: ClassValue): (defaults: string) => string {
6
7
  return (defaults) => {
7
8
  const withBase = cnMerge(defaults, extend)() ?? ''
8
9
  if (typeof ui === 'function') {
9
- return ui(withBase)
10
+ return cnMerge(ui(withBase))() ?? ''
10
11
  }
11
12
  return cnMerge(withBase, ui)() ?? ''
12
13
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@falcondev-oss/nuxt-layers-base",
3
3
  "type": "module",
4
- "version": "0.43.1",
4
+ "version": "0.43.3",
5
5
  "description": "Nuxt layer with lots of useful helpers and @nuxt/ui components",
6
6
  "license": "MIT",
7
7
  "repository": {