@falcondev-oss/nuxt-layers-base 0.40.3 → 0.41.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.
- package/app/app.tsx +14 -0
- package/app/assets/css/base.css +6 -0
- package/app/components/layout/LayoutAuth.tsx +22 -0
- package/app/components/layout/LayoutNavbar.tsx +193 -0
- package/app/components/layout/LayoutPage.tsx +183 -0
- package/app/components/layout/LayoutSidebar.tsx +167 -0
- package/app/components/modals/ConfirmModal.tsx +80 -0
- package/app/components/modals/EntityCrudModal.tsx +124 -0
- package/app/components/overlay/modal/ModalActions.tsx +36 -0
- package/app/components/overlay/modal/ModalConfirm.tsx +46 -0
- package/app/components/overlay/modal/ModalRadioGroup.tsx +62 -0
- package/app/components/u/UActions.tsx +30 -0
- package/app/components/u/UAuthForm.tsx +30 -0
- package/app/components/u/UCustomApp.tsx +33 -0
- package/app/components/u/UField.tsx +211 -0
- package/app/components/u/UForm.tsx +133 -0
- package/app/components/u/UImageWithFallback.tsx +47 -0
- package/app/components/u/UInputDatePicker.tsx +94 -0
- package/app/components/u/UInputDateRangePicker.tsx +143 -0
- package/app/components/u/UInputDurationMinutes.tsx +106 -0
- package/app/components/u/UInputFile.tsx +132 -0
- package/app/components/u/URibbonCard.tsx +54 -0
- package/app/components/u/URibbonSection.tsx +36 -0
- package/app/components/u/USwitchCard.tsx +64 -0
- package/app/components/util/Define.tsx +21 -0
- package/app/components/util/SlotRef.tsx +21 -0
- package/app/components/util/Sync.tsx +37 -0
- package/app/components/util/TemplateText.tsx +31 -0
- package/app/composables/confirm.ts +1 -1
- package/app/error.tsx +24 -0
- package/app/utils/define-setup-component.ts +39 -5
- package/nuxt.config.ts +8 -0
- package/package.json +4 -3
- package/app/app.vue +0 -3
- package/app/components/layout/LayoutAuth.vue +0 -13
- package/app/components/layout/LayoutNavbar.vue +0 -140
- package/app/components/layout/LayoutPage.vue +0 -174
- package/app/components/layout/LayoutSidebar.vue +0 -117
- package/app/components/modals/ConfirmModal.vue +0 -65
- package/app/components/modals/EntityCrudModal.vue +0 -109
- package/app/components/overlay/modal/ModalActions.vue +0 -25
- package/app/components/overlay/modal/ModalConfirm.vue +0 -25
- package/app/components/overlay/modal/ModalRadioGroup.vue +0 -37
- package/app/components/u/UActions.vue +0 -23
- package/app/components/u/UAuthForm.vue +0 -36
- package/app/components/u/UCustomApp.vue +0 -23
- package/app/components/u/UField.vue +0 -191
- package/app/components/u/UForm.vue +0 -96
- package/app/components/u/UImageWithFallback.vue +0 -35
- package/app/components/u/UInputDatePicker.vue +0 -79
- package/app/components/u/UInputDateRangePicker.vue +0 -100
- package/app/components/u/UInputDurationMinutes.vue +0 -90
- package/app/components/u/UInputFile.vue +0 -127
- package/app/components/u/URibbonCard.vue +0 -43
- package/app/components/u/URibbonSection.vue +0 -29
- package/app/components/u/USwitchCard.vue +0 -51
- package/app/components/util/Define.vue +0 -13
- package/app/components/util/SlotRef.vue +0 -17
- package/app/components/util/Sync.vue +0 -24
- package/app/components/util/TemplateText.vue +0 -20
- package/app/error.vue +0 -15
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import type { CardProps, CardSlots } from '@nuxt/ui'
|
|
2
|
+
import type { VNode } from 'vue'
|
|
3
|
+
import { useForwardProps } from 'reka-ui'
|
|
4
|
+
import { omit } from 'remeda'
|
|
5
|
+
import { UCard } from '#components'
|
|
6
|
+
import { mergeSlotClass } from '../../utils/ui'
|
|
7
|
+
|
|
8
|
+
export default defineSetupComponent(
|
|
9
|
+
(_: {
|
|
10
|
+
props: CardProps
|
|
11
|
+
slots: CardSlots & {
|
|
12
|
+
/** Ribbon strip above the body — fill it with `URibbonSection`s. */
|
|
13
|
+
ribbon?: () => VNode[]
|
|
14
|
+
}
|
|
15
|
+
}) =>
|
|
16
|
+
options(_, {
|
|
17
|
+
name: 'URibbonCard',
|
|
18
|
+
props: ['as', 'title', 'description', 'variant', 'class', 'ui'],
|
|
19
|
+
emits: [],
|
|
20
|
+
inheritAttrs: false,
|
|
21
|
+
setup: (props, { slots, attrs }) => {
|
|
22
|
+
const forwarded = useForwardProps(props)
|
|
23
|
+
|
|
24
|
+
return () => (
|
|
25
|
+
<div class="flex flex-col">
|
|
26
|
+
{slots.ribbon ? (
|
|
27
|
+
// the ribbon's lower edge runs behind the card, so the card keeps its own rounded top
|
|
28
|
+
<div
|
|
29
|
+
class={[
|
|
30
|
+
'divide-default ring-default bg-elevated flex items-stretch divide-x overflow-x-auto 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',
|
|
31
|
+
slots.default ? '-mb-2 pb-2' : 'rounded-b-lg',
|
|
32
|
+
]}
|
|
33
|
+
>
|
|
34
|
+
{slots.ribbon()}
|
|
35
|
+
</div>
|
|
36
|
+
) : null}
|
|
37
|
+
|
|
38
|
+
{slots.default ? (
|
|
39
|
+
<UCard
|
|
40
|
+
{...attrs}
|
|
41
|
+
{...forwarded.value}
|
|
42
|
+
ui={{
|
|
43
|
+
...forwarded.value.ui,
|
|
44
|
+
body: mergeSlotClass(forwarded.value.ui?.body, 'p-0!'),
|
|
45
|
+
}}
|
|
46
|
+
class="ring-accented relative shadow-[0_-2px_3px_-1px_rgb(0_0_0/0.08)]"
|
|
47
|
+
v-slots={vSlots(UCard, omit(slots, ['ribbon']))}
|
|
48
|
+
/>
|
|
49
|
+
) : null}
|
|
50
|
+
</div>
|
|
51
|
+
)
|
|
52
|
+
},
|
|
53
|
+
}),
|
|
54
|
+
)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { VNode } from 'vue'
|
|
2
|
+
|
|
3
|
+
export default defineSetupComponent(
|
|
4
|
+
(_: {
|
|
5
|
+
props: {
|
|
6
|
+
title?: string
|
|
7
|
+
end?: boolean
|
|
8
|
+
}
|
|
9
|
+
slots: {
|
|
10
|
+
default: () => VNode[]
|
|
11
|
+
}
|
|
12
|
+
}) =>
|
|
13
|
+
options(_, {
|
|
14
|
+
name: 'URibbonSection',
|
|
15
|
+
inheritAttrs: false,
|
|
16
|
+
props: ['title', 'end'],
|
|
17
|
+
emits: [],
|
|
18
|
+
setup:
|
|
19
|
+
(props, { slots, attrs }) =>
|
|
20
|
+
() => (
|
|
21
|
+
<div
|
|
22
|
+
data-end={props.end || undefined}
|
|
23
|
+
class={[
|
|
24
|
+
'flex shrink-0 flex-col gap-1.5 px-3 py-2',
|
|
25
|
+
props.end &&
|
|
26
|
+
'border-default [&:not([data-end]~*)]:ml-auto [&:not([data-end]~*)]:border-l',
|
|
27
|
+
]}
|
|
28
|
+
>
|
|
29
|
+
{props.title ? <p class="text-dimmed text-[10px] leading-none">{props.title}</p> : null}
|
|
30
|
+
<div class="flex flex-1 flex-wrap items-center gap-1" {...attrs}>
|
|
31
|
+
{slots.default?.()}
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
),
|
|
35
|
+
}),
|
|
36
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { VNode } from 'vue'
|
|
2
|
+
import { UCard, UIcon, USwitch } from '#components'
|
|
3
|
+
|
|
4
|
+
export default defineSetupComponent(
|
|
5
|
+
(_: {
|
|
6
|
+
props: {
|
|
7
|
+
title: string
|
|
8
|
+
modelValue: boolean
|
|
9
|
+
icon?: string
|
|
10
|
+
loading?: boolean
|
|
11
|
+
alwaysExpanded?: boolean
|
|
12
|
+
}
|
|
13
|
+
emits: {
|
|
14
|
+
'toggle': (value: boolean, controller: AbortController) => void
|
|
15
|
+
'update:modelValue': (value: boolean) => void
|
|
16
|
+
}
|
|
17
|
+
slots: {
|
|
18
|
+
default: () => VNode[]
|
|
19
|
+
}
|
|
20
|
+
}) =>
|
|
21
|
+
options(_, {
|
|
22
|
+
name: 'USwitchCard',
|
|
23
|
+
props: ['title', 'modelValue', 'icon', 'loading', 'alwaysExpanded'],
|
|
24
|
+
emits: ['toggle', 'update:modelValue'],
|
|
25
|
+
setup: (props, { emit, slots }) => {
|
|
26
|
+
const expanded = computed(() => props.alwaysExpanded || props.modelValue)
|
|
27
|
+
|
|
28
|
+
function onToggle(value: boolean) {
|
|
29
|
+
const controller = new AbortController()
|
|
30
|
+
emit('toggle', value, controller)
|
|
31
|
+
if (controller.signal.aborted) return
|
|
32
|
+
|
|
33
|
+
emit('update:modelValue', value)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return () => (
|
|
37
|
+
<UCard
|
|
38
|
+
class="m-px"
|
|
39
|
+
variant="subtle"
|
|
40
|
+
ui={{
|
|
41
|
+
header: `text-sm font-semibold py-2 px-3! ${expanded.value ? '' : 'border-b-0'}`,
|
|
42
|
+
body: `p-3! ${expanded.value ? '' : 'hidden'}`,
|
|
43
|
+
}}
|
|
44
|
+
v-slots={vSlots(UCard, {
|
|
45
|
+
header: () => [
|
|
46
|
+
<div class="flex items-center justify-between gap-8">
|
|
47
|
+
<span class="flex items-center gap-2">
|
|
48
|
+
{props.icon ? <UIcon name={props.icon} class="text-muted size-5" /> : null}
|
|
49
|
+
{props.title}
|
|
50
|
+
</span>
|
|
51
|
+
<USwitch
|
|
52
|
+
modelValue={props.modelValue}
|
|
53
|
+
loading={props.loading}
|
|
54
|
+
onUpdate:modelValue={(value) => onToggle(value)}
|
|
55
|
+
/>
|
|
56
|
+
</div>,
|
|
57
|
+
],
|
|
58
|
+
default: () => slots.default?.() ?? [],
|
|
59
|
+
})}
|
|
60
|
+
/>
|
|
61
|
+
)
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { VNode } from 'vue'
|
|
2
|
+
|
|
3
|
+
export default defineSetupComponent(
|
|
4
|
+
<T extends object>(_: {
|
|
5
|
+
props: {
|
|
6
|
+
value: T
|
|
7
|
+
}
|
|
8
|
+
slots: {
|
|
9
|
+
default: (props: T) => VNode[]
|
|
10
|
+
}
|
|
11
|
+
}) =>
|
|
12
|
+
options(_, {
|
|
13
|
+
name: 'Define',
|
|
14
|
+
props: ['value'],
|
|
15
|
+
emits: [],
|
|
16
|
+
setup:
|
|
17
|
+
(props, { slots }) =>
|
|
18
|
+
() =>
|
|
19
|
+
slots.default?.(props.value),
|
|
20
|
+
}),
|
|
21
|
+
)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { ComponentPublicInstance, VNode } from 'vue'
|
|
2
|
+
|
|
3
|
+
export default defineSetupComponent(
|
|
4
|
+
(_: {
|
|
5
|
+
props: {
|
|
6
|
+
slotRef?: ComponentPublicInstance
|
|
7
|
+
}
|
|
8
|
+
slots: {
|
|
9
|
+
default: () => VNode[]
|
|
10
|
+
}
|
|
11
|
+
}) =>
|
|
12
|
+
options(_, {
|
|
13
|
+
name: 'SlotRef',
|
|
14
|
+
props: ['slotRef'],
|
|
15
|
+
emits: [],
|
|
16
|
+
setup: (props) => () => {
|
|
17
|
+
const slot = props.slotRef?.$slots.default
|
|
18
|
+
return slot ? h(slot) : null
|
|
19
|
+
},
|
|
20
|
+
}),
|
|
21
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
function renderNothing() {
|
|
2
|
+
return null
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export default defineSetupComponent(
|
|
6
|
+
<T,>(_: {
|
|
7
|
+
props: {
|
|
8
|
+
input: T
|
|
9
|
+
output: T
|
|
10
|
+
deep?: boolean
|
|
11
|
+
once?: boolean
|
|
12
|
+
}
|
|
13
|
+
emits: {
|
|
14
|
+
'update:output': (value: T) => void
|
|
15
|
+
}
|
|
16
|
+
}) =>
|
|
17
|
+
options(_, {
|
|
18
|
+
name: 'Sync',
|
|
19
|
+
props: ['input', 'output', 'deep', 'once'],
|
|
20
|
+
emits: ['update:output'],
|
|
21
|
+
setup: (props, { emit }) => {
|
|
22
|
+
watch(
|
|
23
|
+
() => props.input,
|
|
24
|
+
() => {
|
|
25
|
+
emit('update:output', props.input)
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
immediate: true,
|
|
29
|
+
deep: props.deep,
|
|
30
|
+
once: props.once,
|
|
31
|
+
},
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return renderNothing
|
|
35
|
+
},
|
|
36
|
+
}),
|
|
37
|
+
)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { templateParts } from '../../utils/display'
|
|
2
|
+
import { UBadge } from '#components'
|
|
3
|
+
|
|
4
|
+
export default defineSetupComponent(
|
|
5
|
+
(_: {
|
|
6
|
+
props: {
|
|
7
|
+
text: ReturnType<typeof templateParts>
|
|
8
|
+
}
|
|
9
|
+
}) =>
|
|
10
|
+
options(_, {
|
|
11
|
+
name: 'TemplateText',
|
|
12
|
+
props: ['text'],
|
|
13
|
+
emits: [],
|
|
14
|
+
setup: (props) => () => (
|
|
15
|
+
<p>
|
|
16
|
+
{props.text.strings.map((string, index) => (
|
|
17
|
+
<span key={index}>
|
|
18
|
+
<span>{string}</span>
|
|
19
|
+
{props.text.values[index] ? (
|
|
20
|
+
<strong class="font-semibold">
|
|
21
|
+
<UBadge variant="soft" color="neutral" size="lg" class="h-6! p-1! font-semibold">
|
|
22
|
+
{props.text.values[index]}
|
|
23
|
+
</UBadge>
|
|
24
|
+
</strong>
|
|
25
|
+
) : null}
|
|
26
|
+
</span>
|
|
27
|
+
))}
|
|
28
|
+
</p>
|
|
29
|
+
),
|
|
30
|
+
}),
|
|
31
|
+
)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RadioGroupItem } from '@nuxt/ui'
|
|
2
|
-
import type { ConfirmModalProps } from '../components/modals/ConfirmModal
|
|
2
|
+
import type { ConfirmModalProps } from '../components/modals/ConfirmModal'
|
|
3
3
|
import { LazyConfirmModal } from '#components'
|
|
4
4
|
|
|
5
5
|
export type ConfirmModalSuccess<O extends RadioGroupItem[]> = 0 extends O['length']
|
package/app/error.tsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { NuxtError } from '#app'
|
|
2
|
+
import { UApp, UError } from '#components'
|
|
3
|
+
|
|
4
|
+
export default defineSetupComponent(
|
|
5
|
+
(_: {
|
|
6
|
+
props: {
|
|
7
|
+
error: NuxtError
|
|
8
|
+
}
|
|
9
|
+
}) =>
|
|
10
|
+
options(_, {
|
|
11
|
+
name: 'Error',
|
|
12
|
+
props: ['error'],
|
|
13
|
+
emits: [],
|
|
14
|
+
setup: (props) => {
|
|
15
|
+
console.error(props.error)
|
|
16
|
+
|
|
17
|
+
return () => (
|
|
18
|
+
<UApp>
|
|
19
|
+
<UError error={props.error} />
|
|
20
|
+
</UApp>
|
|
21
|
+
)
|
|
22
|
+
},
|
|
23
|
+
}),
|
|
24
|
+
)
|
|
@@ -15,11 +15,14 @@ import { defineComponent } from 'vue'
|
|
|
15
15
|
|
|
16
16
|
declare module 'vue' {
|
|
17
17
|
interface ComponentCustomProps {
|
|
18
|
-
//
|
|
18
|
+
// `v-slots` is what `@vue/babel-plugin-jsx` turns into slot children, so the name has
|
|
19
|
+
// to be spelled that way to reach the runtime at all.
|
|
20
|
+
//
|
|
21
|
+
// Deliberately loose: this only makes `v-slots` an accepted prop name on every
|
|
19
22
|
// component. `Slots` would reject it for any component whose slots are declared as an
|
|
20
23
|
// interface without an index signature (most of @nuxt/ui). The real check is the
|
|
21
24
|
// `vSlots()` helper below, which types the object against the target's own slots.
|
|
22
|
-
|
|
25
|
+
'v-slots'?: Record<string, any>
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -29,7 +32,7 @@ interface ComponentTypes {
|
|
|
29
32
|
slots?: Record<string, any>
|
|
30
33
|
/**
|
|
31
34
|
* Prop names to register when they are deliberately fewer than `keyof props`; the rest
|
|
32
|
-
* arrive as `attrs`.
|
|
35
|
+
* arrive as `attrs`.
|
|
33
36
|
*/
|
|
34
37
|
propKeys?: string
|
|
35
38
|
/**
|
|
@@ -167,7 +170,7 @@ export function defineSetupComponent<const T extends ComponentTypes>(
|
|
|
167
170
|
ComponentOptionsMixin,
|
|
168
171
|
Field<T, 'emits'>,
|
|
169
172
|
// `Partial`, to match `SlotsType<Partial<...>>` below and the partial type `vSlots()`
|
|
170
|
-
PublicProps & {
|
|
173
|
+
PublicProps & { 'v-slots'?: Partial<Field<T, 'slots'>> },
|
|
171
174
|
{},
|
|
172
175
|
false,
|
|
173
176
|
{},
|
|
@@ -190,7 +193,38 @@ type ComponentSlots<T> = T extends new (...args: any) => { $slots: infer S }
|
|
|
190
193
|
? NonNullable<S>
|
|
191
194
|
: {}
|
|
192
195
|
|
|
193
|
-
|
|
196
|
+
/**
|
|
197
|
+
* A slot whose declared type intersects several call signatures, collapsed into one that takes
|
|
198
|
+
* the intersection of their props.
|
|
199
|
+
*
|
|
200
|
+
* `@nuxt/ui` builds `TableSlots` by intersecting a `Record` over header props with one over cell
|
|
201
|
+
* props, each carrying a `string` index signature. The names those `Record`s spell out keep
|
|
202
|
+
* their own props, but any other name — a column id that is no key of the row type — resolves to
|
|
203
|
+
* both signatures at once, which no single function satisfies. Intersecting their props leaves
|
|
204
|
+
* it writable, and its props inferrable.
|
|
205
|
+
*/
|
|
206
|
+
type MergeSlotSignatures<S> = S extends {
|
|
207
|
+
(props: infer A): infer R
|
|
208
|
+
(props: infer B): any
|
|
209
|
+
}
|
|
210
|
+
? // a slot declared with one signature matches the pattern too, inferring the same props
|
|
211
|
+
// twice; rewriting it would turn `() => VNode[]` into `(props: unknown) => VNode[]`
|
|
212
|
+
Same<A, B> extends true
|
|
213
|
+
? S
|
|
214
|
+
: (props: A & B) => R
|
|
215
|
+
: S
|
|
216
|
+
|
|
217
|
+
/** Whether `A` and `B` are the same type, wrapped in tuples so neither side distributes. */
|
|
218
|
+
type Same<A, B> = [A] extends [B] ? ([B] extends [A] ? true : false) : false
|
|
219
|
+
|
|
220
|
+
export function vSlots<C>(
|
|
221
|
+
component: C,
|
|
222
|
+
slots: {
|
|
223
|
+
[Name in keyof ComponentSlots<C>]?: MergeSlotSignatures<ComponentSlots<C>[Name]>
|
|
224
|
+
},
|
|
225
|
+
): Partial<ComponentSlots<C>> {
|
|
226
|
+
// the parameter type only relaxes *how* a slot may be written; what comes back is the target's
|
|
227
|
+
// own slots, which is what its `v-slots` prop is declared as
|
|
194
228
|
return slots
|
|
195
229
|
}
|
|
196
230
|
|
package/nuxt.config.ts
CHANGED
|
@@ -16,6 +16,14 @@ export default defineNuxtConfig({
|
|
|
16
16
|
},
|
|
17
17
|
|
|
18
18
|
// dev
|
|
19
|
+
vite: {
|
|
20
|
+
// `@vitejs/plugin-vue-jsx` only wires up HMR for exports whose initializer calls one of
|
|
21
|
+
// these names. Every component here is `export default defineSetupComponent(...)`, so
|
|
22
|
+
// without this no module is an HMR boundary and every edit escalates to a full reload.
|
|
23
|
+
vueJsx: {
|
|
24
|
+
defineComponentName: ['defineComponent', 'defineSetupComponent'],
|
|
25
|
+
},
|
|
26
|
+
},
|
|
19
27
|
typescript: {
|
|
20
28
|
strict: true,
|
|
21
29
|
tsConfig: {
|
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.
|
|
4
|
+
"version": "0.41.0",
|
|
5
5
|
"description": "Nuxt layer with lots of useful helpers and @nuxt/ui components",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
@@ -57,14 +57,15 @@
|
|
|
57
57
|
"vue-router": "^5.2.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
+
"@types/luxon": "^3.7.5",
|
|
61
|
+
"luxon": "^3.7.2",
|
|
60
62
|
"nuxt": "^4.5.2",
|
|
61
63
|
"typescript": "^5.9.3",
|
|
62
|
-
"vue-tsc": "^3.3.11",
|
|
63
64
|
"zod": "^4.4.3"
|
|
64
65
|
},
|
|
65
66
|
"scripts": {
|
|
66
67
|
"dev": "nuxi dev .playground",
|
|
67
|
-
"type-check": "
|
|
68
|
+
"type-check": "tsc --build",
|
|
68
69
|
"lint": "pnpm -w eslint:cmd \"$PWD\" && pnpm -w prettier:cmd \"$PWD\" --check",
|
|
69
70
|
"lint:fix": "pnpm -w eslint:cmd \"$PWD\" --fix && pnpm -w prettier:cmd \"$PWD\" --write"
|
|
70
71
|
}
|
package/app/app.vue
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
defineSlots<{
|
|
3
|
-
default: any
|
|
4
|
-
}>()
|
|
5
|
-
</script>
|
|
6
|
-
|
|
7
|
-
<template>
|
|
8
|
-
<div class="flex min-h-full flex-col items-center justify-center gap-4 p-4">
|
|
9
|
-
<UPageCard class="w-full max-w-md">
|
|
10
|
-
<slot />
|
|
11
|
-
</UPageCard>
|
|
12
|
-
</div>
|
|
13
|
-
</template>
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import type {
|
|
3
|
-
BreadcrumbItem,
|
|
4
|
-
BreadcrumbProps,
|
|
5
|
-
DashboardNavbarProps,
|
|
6
|
-
DashboardNavbarSlots,
|
|
7
|
-
DashboardPanelProps,
|
|
8
|
-
DashboardToolbarProps,
|
|
9
|
-
NavigationMenuItem,
|
|
10
|
-
NavigationMenuProps,
|
|
11
|
-
} from '@nuxt/ui'
|
|
12
|
-
import type { MaybeRefOrGetter, Ref } from 'vue'
|
|
13
|
-
import type { ToolbarTool } from '../../composables/useToolbar'
|
|
14
|
-
import type { AddPropertyPrefix } from '../../types/helpers'
|
|
15
|
-
import * as R from 'remeda'
|
|
16
|
-
import { toolbarToolsKey } from '../../composables/useToolbar'
|
|
17
|
-
import { mergeSlotClass } from '../../utils/ui'
|
|
18
|
-
|
|
19
|
-
const props = defineProps<{
|
|
20
|
-
panel?: DashboardPanelProps
|
|
21
|
-
navbar?: {
|
|
22
|
-
sidebarToggle?: boolean
|
|
23
|
-
title?: string
|
|
24
|
-
breadcrumb?: BreadcrumbItem[]
|
|
25
|
-
breadcrumbUi?: BreadcrumbProps<BreadcrumbItem>['ui']
|
|
26
|
-
ui?: DashboardNavbarProps['ui']
|
|
27
|
-
}
|
|
28
|
-
tabs?: {
|
|
29
|
-
items?: NavigationMenuItem[]
|
|
30
|
-
ui?: NavigationMenuProps['ui']
|
|
31
|
-
}
|
|
32
|
-
tools?: {
|
|
33
|
-
items?: NavigationMenuItem[]
|
|
34
|
-
ui?: NavigationMenuProps['ui']
|
|
35
|
-
}
|
|
36
|
-
toolbarUi?: DashboardToolbarProps['ui']
|
|
37
|
-
}>()
|
|
38
|
-
|
|
39
|
-
const slots = defineSlots<
|
|
40
|
-
{
|
|
41
|
-
'default': any
|
|
42
|
-
'navbar-title'?: any
|
|
43
|
-
'navbar-trailing'?: any
|
|
44
|
-
'navbar-actions'?: any
|
|
45
|
-
} & AddPropertyPrefix<DashboardNavbarSlots, 'navbar'>
|
|
46
|
-
>()
|
|
47
|
-
|
|
48
|
-
// rendered manually in the `left` slot below
|
|
49
|
-
const omitNavbarSlots = [
|
|
50
|
-
'navbar-left',
|
|
51
|
-
'navbar-leading',
|
|
52
|
-
'navbar-title',
|
|
53
|
-
'navbar-trailing',
|
|
54
|
-
'navbar-right',
|
|
55
|
-
'navbar-actions',
|
|
56
|
-
] satisfies (keyof typeof slots)[]
|
|
57
|
-
const navbarSlots = computed(() =>
|
|
58
|
-
R.pipe(
|
|
59
|
-
slots,
|
|
60
|
-
R.pickBy((_, key) => key.startsWith('navbar-')),
|
|
61
|
-
R.omit(omitNavbarSlots),
|
|
62
|
-
R.mapKeys((key) => key.replace('navbar-', '')),
|
|
63
|
-
),
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
// added by the content rendered inside this navbar through `useToolbar()`
|
|
67
|
-
// `ref()`'s deep unwrapping blows up on the recursive `NavigationMenuItem` type
|
|
68
|
-
const providedTools = ref([]) as Ref<MaybeRefOrGetter<ToolbarTool>[]>
|
|
69
|
-
provide(toolbarToolsKey, providedTools)
|
|
70
|
-
|
|
71
|
-
const toolItems = computed(() => [
|
|
72
|
-
...(props.tools?.items ?? []),
|
|
73
|
-
...providedTools.value.map((tool) => toValue(tool)),
|
|
74
|
-
])
|
|
75
|
-
|
|
76
|
-
const navbarUi = computed<DashboardNavbarProps['ui']>(() => ({
|
|
77
|
-
...props.navbar?.ui,
|
|
78
|
-
toggle: mergeSlotClass(props.navbar?.ui?.toggle, '-ml-1'),
|
|
79
|
-
...(props.navbar?.breadcrumb && {
|
|
80
|
-
root: mergeSlotClass(props.navbar.ui?.root, 'h-auto min-h-(--ui-header-height) py-2'),
|
|
81
|
-
}),
|
|
82
|
-
}))
|
|
83
|
-
</script>
|
|
84
|
-
|
|
85
|
-
<template>
|
|
86
|
-
<UDashboardPanel v-bind="panel">
|
|
87
|
-
<template #header>
|
|
88
|
-
<ForwardSlots v-if="navbar" :slots="navbarSlots">
|
|
89
|
-
<UDashboardNavbar :ui="navbarUi" class="bg-white" :title="navbar.title">
|
|
90
|
-
<template #left>
|
|
91
|
-
<UDashboardSidebarCollapse v-if="navbar.sidebarToggle" class="-ml-1" />
|
|
92
|
-
|
|
93
|
-
<div class="flex min-w-0 flex-col items-start gap-0.5">
|
|
94
|
-
<div class="flex min-w-0 items-center gap-1.5">
|
|
95
|
-
<h1 class="text-highlighted truncate font-semibold">
|
|
96
|
-
<slot name="navbar-title">{{ navbar.title }}</slot>
|
|
97
|
-
</h1>
|
|
98
|
-
<slot name="navbar-trailing" />
|
|
99
|
-
</div>
|
|
100
|
-
|
|
101
|
-
<UBreadcrumb
|
|
102
|
-
v-if="navbar.breadcrumb"
|
|
103
|
-
:items="navbar.breadcrumb"
|
|
104
|
-
:ui="{
|
|
105
|
-
...navbar.breadcrumbUi,
|
|
106
|
-
link: mergeSlotClass(navbar.breadcrumbUi?.link, 'text-xs'),
|
|
107
|
-
separatorIcon: mergeSlotClass(navbar.breadcrumbUi?.separatorIcon, 'size-3.5'),
|
|
108
|
-
}"
|
|
109
|
-
/>
|
|
110
|
-
</div>
|
|
111
|
-
</template>
|
|
112
|
-
|
|
113
|
-
<template #right>
|
|
114
|
-
<!-- eslint-disable-next-line vue/require-explicit-slots -->
|
|
115
|
-
<slot name="navbar-right" />
|
|
116
|
-
<div id="navbar-actions" class="flex items-center gap-2">
|
|
117
|
-
<slot name="navbar-actions" />
|
|
118
|
-
</div>
|
|
119
|
-
</template>
|
|
120
|
-
</UDashboardNavbar>
|
|
121
|
-
</ForwardSlots>
|
|
122
|
-
<UDashboardToolbar
|
|
123
|
-
v-if="tabs || toolItems.length"
|
|
124
|
-
:ui="toolbarUi"
|
|
125
|
-
class="bg-white *:first:-ml-2"
|
|
126
|
-
>
|
|
127
|
-
<template #left>
|
|
128
|
-
<UNavigationMenu v-if="tabs" :items="tabs.items" highlight variant="link" :ui="tabs.ui" />
|
|
129
|
-
<UNavigationMenu v-else :items="toolItems" highlight :ui="tools?.ui" />
|
|
130
|
-
</template>
|
|
131
|
-
<template v-if="tabs" #right>
|
|
132
|
-
<UNavigationMenu :items="toolItems" highlight :ui="tools?.ui" />
|
|
133
|
-
</template>
|
|
134
|
-
</UDashboardToolbar>
|
|
135
|
-
</template>
|
|
136
|
-
<template #body>
|
|
137
|
-
<slot />
|
|
138
|
-
</template>
|
|
139
|
-
</UDashboardPanel>
|
|
140
|
-
</template>
|