@octabits-io/nuxt-ui-kit 0.2.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/LICENSE +21 -0
- package/README.md +151 -0
- package/dist/ai/index.d.ts +255 -0
- package/dist/ai/index.js +389 -0
- package/dist/dates/index.d.ts +42 -0
- package/dist/dates/index.js +108 -0
- package/dist/index.d.ts +453 -0
- package/dist/index.js +526 -0
- package/dist/zod/index.d.ts +24 -0
- package/dist/zod/index.js +17 -0
- package/package.json +102 -0
- package/src/components/AiResultReviewCard.vue +67 -0
- package/src/components/ConfirmDialog.vue +60 -0
- package/src/components/DateInput.vue +66 -0
- package/src/components/DateRangeInput.vue +651 -0
- package/src/components/PeriodDisplay.vue +76 -0
- package/src/components/SubSidebar.vue +85 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Shipped as source: the consumer's Vite compiles this SFC. All imports are
|
|
3
|
+
// explicit — no reliance on the consumer's auto-import configuration.
|
|
4
|
+
// i18n key contract: period.travel.tooltip / period.travel.nights (plural) /
|
|
5
|
+
// period.booking.tooltip / period.booking.days (plural).
|
|
6
|
+
import { computed } from 'vue'
|
|
7
|
+
import { useI18n } from 'vue-i18n'
|
|
8
|
+
import { addDays, differenceInDays } from 'date-fns'
|
|
9
|
+
import UTooltip from '@nuxt/ui/components/Tooltip.vue'
|
|
10
|
+
import UIcon from '@nuxt/ui/components/Icon.vue'
|
|
11
|
+
import { calculateDays, createDateFormatter, type Period } from '@octabits-io/nuxt-ui-kit/dates'
|
|
12
|
+
|
|
13
|
+
const props = withDefaults(
|
|
14
|
+
defineProps<{
|
|
15
|
+
/** ISO date range. `end` is the last booked day (not the checkout date). */
|
|
16
|
+
period: Period
|
|
17
|
+
/**
|
|
18
|
+
* `travel` → check-in → check-out, counted in nights (guest stay).
|
|
19
|
+
* `booking` → check-in → last booked day, counted in days (occupancy).
|
|
20
|
+
*/
|
|
21
|
+
kind: 'travel' | 'booking'
|
|
22
|
+
/** Render the `· N nights/days` suffix. */
|
|
23
|
+
showCount?: boolean
|
|
24
|
+
size?: 'xs' | 'sm' | 'md'
|
|
25
|
+
}>(),
|
|
26
|
+
{ showCount: true, size: 'sm' },
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const { t, locale } = useI18n()
|
|
30
|
+
const { formatDate, formatCheckoutDate } = createDateFormatter({ getLocale: () => locale.value })
|
|
31
|
+
|
|
32
|
+
const isTravel = computed(() => props.kind === 'travel')
|
|
33
|
+
|
|
34
|
+
const icon = computed(() => (isTravel.value ? 'i-lucide-luggage' : 'i-lucide-calendar-check'))
|
|
35
|
+
const tooltip = computed(() => t(isTravel.value ? 'period.travel.tooltip' : 'period.booking.tooltip'))
|
|
36
|
+
|
|
37
|
+
const startText = computed(() => formatDate(props.period.start))
|
|
38
|
+
const endText = computed(() =>
|
|
39
|
+
isTravel.value
|
|
40
|
+
? formatCheckoutDate(props.period.end)
|
|
41
|
+
: formatDate(props.period.end),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
const count = computed(() =>
|
|
45
|
+
isTravel.value
|
|
46
|
+
? differenceInDays(
|
|
47
|
+
addDays(new Date(props.period.end), 1),
|
|
48
|
+
new Date(props.period.start),
|
|
49
|
+
)
|
|
50
|
+
: calculateDays(props.period),
|
|
51
|
+
)
|
|
52
|
+
const countText = computed(() =>
|
|
53
|
+
isTravel.value
|
|
54
|
+
? t('period.travel.nights', count.value)
|
|
55
|
+
: t('period.booking.days', count.value),
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
const iconSize = computed(() =>
|
|
59
|
+
props.size === 'xs' ? 'size-3.5' : props.size === 'md' ? 'size-5' : 'size-4',
|
|
60
|
+
)
|
|
61
|
+
const textSize = computed(() =>
|
|
62
|
+
props.size === 'xs' ? 'text-xs' : props.size === 'md' ? 'text-base' : 'text-sm',
|
|
63
|
+
)
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<template>
|
|
67
|
+
<div class="flex flex-wrap items-center gap-x-1.5 gap-y-0.5" :class="textSize">
|
|
68
|
+
<span class="inline-flex items-center gap-1.5 whitespace-nowrap">
|
|
69
|
+
<UTooltip :text="tooltip">
|
|
70
|
+
<UIcon :name="icon" class="shrink-0 text-muted" :class="iconSize" :aria-label="tooltip" />
|
|
71
|
+
</UTooltip>
|
|
72
|
+
{{ startText }} – {{ endText }}
|
|
73
|
+
</span>
|
|
74
|
+
<span v-if="showCount" class="whitespace-nowrap text-muted">· {{ countText }}</span>
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
// Shipped as source: the consumer's Vite compiles this SFC. All imports are
|
|
3
|
+
// explicit — no reliance on the consumer's auto-import configuration.
|
|
4
|
+
import { ref, computed, watch } from 'vue'
|
|
5
|
+
import { useRoute } from 'vue-router'
|
|
6
|
+
import USlideover from '@nuxt/ui/components/Slideover.vue'
|
|
7
|
+
import UButton from '@nuxt/ui/components/Button.vue'
|
|
8
|
+
import UIcon from '@nuxt/ui/components/Icon.vue'
|
|
9
|
+
|
|
10
|
+
const props = withDefaults(defineProps<{
|
|
11
|
+
title: string
|
|
12
|
+
toggleLabel?: string
|
|
13
|
+
width?: string
|
|
14
|
+
loading?: boolean
|
|
15
|
+
headerless?: boolean
|
|
16
|
+
/**
|
|
17
|
+
* Query param that carries the current selection (sidebar-view convention,
|
|
18
|
+
* e.g. `?s=owner:42`) — the mobile slideover auto-closes when it changes.
|
|
19
|
+
*/
|
|
20
|
+
selectionQueryKey?: string
|
|
21
|
+
}>(), {
|
|
22
|
+
width: 'w-[240px]',
|
|
23
|
+
selectionQueryKey: 's',
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const open = ref(false)
|
|
27
|
+
const route = useRoute()
|
|
28
|
+
|
|
29
|
+
const buttonLabel = computed(() => props.toggleLabel ?? props.title)
|
|
30
|
+
|
|
31
|
+
watch(
|
|
32
|
+
() => [route.path, route.query[props.selectionQueryKey]],
|
|
33
|
+
() => { open.value = false },
|
|
34
|
+
)
|
|
35
|
+
</script>
|
|
36
|
+
|
|
37
|
+
<template>
|
|
38
|
+
<div class="flex h-full w-full flex-col overflow-hidden">
|
|
39
|
+
<!-- Page header zone: rendered once (unlike #sidebar), full width above both columns -->
|
|
40
|
+
<div v-if="$slots.header" class="shrink-0">
|
|
41
|
+
<slot name="header" />
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div class="flex min-h-0 w-full flex-1 overflow-hidden">
|
|
45
|
+
<USlideover v-model:open="open" side="left" :title="title">
|
|
46
|
+
<template #body>
|
|
47
|
+
<div class="flex h-full flex-col">
|
|
48
|
+
<div v-if="loading" class="flex flex-1 items-center justify-center">
|
|
49
|
+
<UIcon name="i-lucide-loader-2" class="size-6 animate-spin text-primary" />
|
|
50
|
+
</div>
|
|
51
|
+
<slot v-else name="sidebar" />
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
</USlideover>
|
|
55
|
+
|
|
56
|
+
<aside
|
|
57
|
+
:class="['hidden shrink-0 flex-col overflow-hidden border-r border-default lg:flex', width]"
|
|
58
|
+
>
|
|
59
|
+
<div v-if="!headerless" class="border-b border-default px-4 py-3">
|
|
60
|
+
<h2 class="font-display text-lg font-semibold tracking-tight">{{ title }}</h2>
|
|
61
|
+
</div>
|
|
62
|
+
<div v-if="loading" class="flex flex-1 items-center justify-center">
|
|
63
|
+
<UIcon name="i-lucide-loader-2" class="size-6 animate-spin text-primary" />
|
|
64
|
+
</div>
|
|
65
|
+
<slot v-else name="sidebar" />
|
|
66
|
+
</aside>
|
|
67
|
+
|
|
68
|
+
<div class="flex min-w-0 flex-1 flex-col overflow-hidden">
|
|
69
|
+
<div class="flex shrink-0 items-center gap-2 border-b border-default px-3 py-2 lg:hidden">
|
|
70
|
+
<UButton
|
|
71
|
+
:label="buttonLabel"
|
|
72
|
+
icon="i-lucide-panel-left-open"
|
|
73
|
+
color="neutral"
|
|
74
|
+
variant="outline"
|
|
75
|
+
size="sm"
|
|
76
|
+
@click="open = true"
|
|
77
|
+
/>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="min-w-0 flex-1 overflow-y-auto">
|
|
80
|
+
<slot />
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</template>
|