nuxt-unified-ui 0.1.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/README.md ADDED
@@ -0,0 +1,19 @@
1
+ # Unified Nuxt UI
2
+
3
+ A reuseable Nuxt layer which integrates Nuxt UI and some other useful libraries into your Nuxt application.
4
+
5
+ ## Setup
6
+
7
+ Make sure to install the dependencies:
8
+
9
+ ```bash
10
+ bun i
11
+ ```
12
+
13
+ ## Development Server
14
+
15
+ Start the development server on http://localhost:8080
16
+
17
+ ```bash
18
+ bun dev
19
+ ```
@@ -0,0 +1,49 @@
1
+
2
+
3
+ export default defineAppConfig({
4
+ ui: {
5
+
6
+ badge: {
7
+ defaultVariants: {
8
+ color: 'neutral',
9
+ },
10
+ },
11
+
12
+ button: {
13
+ defaultVariants: {
14
+ color: 'neutral',
15
+ },
16
+ },
17
+
18
+ calendar: {
19
+ defaultVariants: {
20
+ color: 'neutral',
21
+ },
22
+ },
23
+
24
+ card: {
25
+ slots: {
26
+ body: 'p-3 sm:p-3',
27
+ },
28
+ },
29
+
30
+ input: {
31
+ defaultVariants: {
32
+ color: 'neutral',
33
+ },
34
+ },
35
+
36
+ select: {
37
+ defaultVariants: {
38
+ color: 'neutral',
39
+ },
40
+ },
41
+
42
+ tabs: {
43
+ defaultVariants: {
44
+ color: 'neutral',
45
+ },
46
+ },
47
+
48
+ },
49
+ });
@@ -0,0 +1,24 @@
1
+ @import "tailwindcss" source("../../..");
2
+ @import "@nuxt/ui";
3
+
4
+
5
+ /* localization */
6
+
7
+ @utility rtl {
8
+ direction: rtl;
9
+ }
10
+
11
+ @utility ltr {
12
+ direction: ltr;
13
+ }
14
+
15
+
16
+ /* fixes */
17
+
18
+ body {
19
+ @apply overflow-y-auto! ps-0!;
20
+ }
21
+
22
+ table thead tr th {
23
+ @apply text-start whitespace-nowrap;
24
+ }
@@ -0,0 +1,67 @@
1
+ <script setup>
2
+
3
+ /* interface */
4
+
5
+ const props = defineProps({
6
+ target: Object,
7
+ fields: Array,
8
+ });
9
+
10
+
11
+ /* elements */
12
+
13
+ import FormElementText from '../elements/form-element-text.vue';
14
+ import FormElementSelect from '../elements/form-element-select.vue';
15
+ import FormElementSeries from '../elements/form-element-series.vue';
16
+ import FormElementCheckbox from '../elements/form-element-checkbox.vue';
17
+ import FormElementDate from '../elements/form-element-date.vue';
18
+
19
+
20
+ const elementsMap = {
21
+ 'text': FormElementText,
22
+ 'select': FormElementSelect,
23
+ 'series': FormElementSeries,
24
+ 'date': FormElementDate,
25
+ 'checkbox': FormElementCheckbox,
26
+ };
27
+
28
+
29
+ /* v-if */
30
+
31
+ import { matches } from 'unified-mongo-filter';
32
+
33
+ const filteredFields = computed(() => {
34
+ return props.fields?.filter(it =>
35
+ !it.vIf || matches(it.vIf, props.target)
36
+ );
37
+ });
38
+
39
+ </script>
40
+
41
+
42
+ <template>
43
+ <div class="grid grid-cols-12 gap-3">
44
+ <div
45
+ v-for="field of filteredFields" :key="field.key"
46
+ :class="{
47
+ 'col-span-12': field.width === 12 || !field.width,
48
+ 'col-span-11': field.width === 11,
49
+ 'col-span-10': field.width === 10,
50
+ 'col-span-9': field.width === 9,
51
+ 'col-span-8': field.width === 8,
52
+ 'col-span-7': field.width === 7,
53
+ 'col-span-6': field.width === 6,
54
+ 'col-span-5': field.width === 5,
55
+ 'col-span-4': field.width === 4,
56
+ 'col-span-3': field.width === 3,
57
+ 'col-span-2': field.width === 2,
58
+ 'col-span-1': field.width === 1,
59
+ }">
60
+ <component
61
+ :is="elementsMap[field.identifier]"
62
+ :field="field"
63
+ v-model="props.target[field.key]"
64
+ />
65
+ </div>
66
+ </div>
67
+ </template>
@@ -0,0 +1,6 @@
1
+ <template>
2
+ <u-icon
3
+ name="lucide:loader-circle"
4
+ class="animate-spin"
5
+ />
6
+ </template>
@@ -0,0 +1,93 @@
1
+ <script setup>
2
+
3
+ /* interface */
4
+
5
+ const props = defineProps({
6
+ icon: {
7
+ type: String,
8
+ },
9
+ iconClasses: {
10
+ type: String,
11
+ },
12
+ title: {
13
+ type: String,
14
+ },
15
+ titleClasses: {
16
+ type: String,
17
+ },
18
+ subtitle: {
19
+ type: String,
20
+ },
21
+ subtitleClasses: {
22
+ type: String,
23
+ },
24
+ text: {
25
+ type: String,
26
+ },
27
+ textClasses: {
28
+ type: String,
29
+ },
30
+ });
31
+
32
+ const slots = useSlots();
33
+
34
+
35
+ /* flags */
36
+
37
+ const shouldShow = computed(() => {
38
+ return props.icon || props.title || props.subtitle || props.text || slots.append;
39
+ });
40
+
41
+ </script>
42
+
43
+
44
+ <template>
45
+ <div v-if="shouldShow">
46
+
47
+ <div v-if="props.title || props.subtitle || props.icon || slots.append" class="flex items-start">
48
+ <div v-if="props.title || props.icon" class="flex items-center" style="gap: 0.5em;">
49
+ <u-icon
50
+ v-if="props.icon"
51
+ :name="props.icon"
52
+ :class="props.iconClasses"
53
+ style="width: 1.3em; height: 1.3em; flex-shrink: 0; flex-grow: 0;"
54
+ />
55
+ <h1
56
+ v-if="props.title"
57
+ class="font-medium"
58
+ :class="props.titleClasses"
59
+ style="font-size: 1.3em;">
60
+ {{ props.title }}
61
+ </h1>
62
+ </div>
63
+ <template v-if="$slots.append">
64
+ <div class="mt-1 ms-auto">
65
+ <slot name="append" />
66
+ </div>
67
+ </template>
68
+ </div>
69
+
70
+ <h2
71
+ v-if="props.subtitle"
72
+ class="font-light"
73
+ :class="props.subtitleClasses"
74
+ style="font-size: 0.9em;"
75
+ :style="{
76
+ marginInlineStart: props.icon ? '2em' : '0',
77
+ }">
78
+ {{ props.subtitle }}
79
+ </h2>
80
+
81
+ <p
82
+ v-if="props.text"
83
+ :class="[
84
+ {
85
+ 'mt-2': props.title || props.subtitle,
86
+ },
87
+ props.textClasses
88
+ ]">
89
+ {{ props.text }}
90
+ </p>
91
+
92
+ </div>
93
+ </template>
@@ -0,0 +1,30 @@
1
+ import { UnForm } from '#components';
2
+
3
+
4
+ interface IOptions {
5
+ target?: MaybeRefOrGetter<any>;
6
+ fields: MaybeRefOrGetter<any[]>;
7
+ }
8
+
9
+ export function useForm(options: IOptions) {
10
+
11
+ const formData: Ref<any> = toRef(options.target || {});
12
+ const formFields = computed(() => toValue(options.fields) || []);
13
+
14
+ const FormTag = defineComponent({
15
+ setup() {
16
+ return () => {
17
+ return h(UnForm, {
18
+ target: toValue(formData),
19
+ fields: toValue(formFields),
20
+ });
21
+ };
22
+ },
23
+ });
24
+
25
+ return {
26
+ form: formData,
27
+ formTag: FormTag,
28
+ };
29
+
30
+ }
@@ -0,0 +1,94 @@
1
+ <script setup>
2
+
3
+ /* interface */
4
+
5
+ const props = defineProps({
6
+ icon: {
7
+ type: String,
8
+ },
9
+ title: {
10
+ type: String,
11
+ },
12
+ subtitle: {
13
+ type: String,
14
+ },
15
+ text: {
16
+ type: String,
17
+ },
18
+ startButtons: {
19
+ type: Array,
20
+ default: () => [
21
+ {
22
+ label: 'Submit',
23
+ value: true,
24
+ }
25
+ ],
26
+ },
27
+ endButtons: {
28
+ type: Array,
29
+ default: () => [
30
+ {
31
+ variant: 'ghost',
32
+ label: 'Cancel',
33
+ value: false,
34
+ }
35
+ ],
36
+ },
37
+ });
38
+
39
+ const emit = defineEmits([
40
+ 'close',
41
+ ]);
42
+
43
+
44
+ /* actions */
45
+
46
+ async function handleButtonClick(button) {
47
+
48
+ if (button.onClick) {
49
+ await button.onClick(button.value);
50
+ }
51
+
52
+ emit('close', button.value);
53
+
54
+ }
55
+
56
+ </script>
57
+
58
+
59
+ <template>
60
+ <u-modal @update:open="!$event && emit('close')">
61
+ <template #content>
62
+ <u-card>
63
+
64
+ <un-typography
65
+ :icon="props.icon"
66
+ :title="props.title"
67
+ :subtitle="props.subtitle"
68
+ :text="props.text"
69
+ />
70
+
71
+ <div class="flex items-end gap-2 mt-4">
72
+
73
+ <u-button
74
+ v-for="button of props.startButtons" :key="button.value || button.label || button.icon"
75
+ v-bind="radOmit(button, [ 'value', 'onClick' ])"
76
+ loading-auto
77
+ @click="handleButtonClick(button)"
78
+ />
79
+
80
+ <div class="grow" />
81
+
82
+ <u-button
83
+ v-for="button of props.endButtons" :key="button.value || button.label || button.icon"
84
+ v-bind="radOmit(button, [ 'value', 'onClick' ])"
85
+ loading-auto
86
+ @click="handleButtonClick(button)"
87
+ />
88
+
89
+ </div>
90
+
91
+ </u-card>
92
+ </template>
93
+ </u-modal>
94
+ </template>
@@ -0,0 +1,101 @@
1
+ <script setup>
2
+
3
+ /* interface */
4
+
5
+ const props = defineProps({
6
+ icon: {
7
+ type: String,
8
+ },
9
+ title: {
10
+ type: String,
11
+ },
12
+ subtitle: {
13
+ type: String,
14
+ },
15
+ text: {
16
+ type: String,
17
+ },
18
+ fields: {
19
+ type: Array,
20
+ required: true,
21
+ },
22
+ initialForm: {
23
+ type: Object,
24
+ },
25
+ submitButton: {
26
+ type: Object,
27
+ },
28
+ cancelButton: {
29
+ type: Object,
30
+ },
31
+ });
32
+
33
+ const emit = defineEmits([
34
+ 'close',
35
+ ]);
36
+
37
+
38
+ /* form */
39
+
40
+ const { form, formTag } = useForm({
41
+ target: !props.initialForm ? undefined : JSON.parse(JSON.stringify(props.initialForm)),
42
+ fields: () => props.fields,
43
+ });
44
+
45
+
46
+ /* actions */
47
+
48
+ async function handleSubmit() {
49
+
50
+ if (props.submitButton?.onClick) {
51
+ await props.submitButton?.onClick(form.value);
52
+ }
53
+
54
+ emit('close', form.value);
55
+
56
+ }
57
+
58
+ </script>
59
+
60
+
61
+ <template>
62
+ <u-modal @update:open="!$event && emit('close')">
63
+ <template #content>
64
+ <u-card>
65
+
66
+ <un-typography
67
+ :icon="props.icon"
68
+ :title="props.title"
69
+ :subtitle="props.subtitle"
70
+ :text="props.text"
71
+ />
72
+
73
+ <form-tag
74
+ class="mt-4"
75
+ />
76
+
77
+ <div class="flex items-end gap-2 mt-4">
78
+
79
+ <u-button
80
+ label="Submit"
81
+ v-bind="radOmit(props.submitButton, [ 'onClick' ])"
82
+ loading-auto
83
+ @click="handleSubmit()"
84
+ />
85
+
86
+ <div class="grow" />
87
+
88
+ <u-button
89
+ variant="ghost"
90
+ label="Cancel"
91
+ v-bind="radOmit(props.cancelButton, [ 'onClick' ])"
92
+ loading-auto
93
+ @click="emit('close')"
94
+ />
95
+
96
+ </div>
97
+
98
+ </u-card>
99
+ </template>
100
+ </u-modal>
101
+ </template>
@@ -0,0 +1,22 @@
1
+ <script setup>
2
+
3
+ /* interface */
4
+
5
+ const props = defineProps({
6
+ field: Object,
7
+ });
8
+
9
+ const modelValue = defineModel();
10
+
11
+ </script>
12
+
13
+
14
+ <template>
15
+ <u-form-field v-bind="radPick(props.field, [ 'fieldLabel' ])">
16
+ <u-checkbox
17
+ class="w-full"
18
+ v-bind="radOmit(props.field, [ 'key', 'identifier', 'fieldLabel' ])"
19
+ v-model="modelValue"
20
+ />
21
+ </u-form-field>
22
+ </template>
@@ -0,0 +1,70 @@
1
+ <script setup>
2
+
3
+ /* interface */
4
+
5
+ const props = defineProps({
6
+ field: Object,
7
+ });
8
+
9
+ const modelValue = defineModel();
10
+
11
+
12
+ /* dates */
13
+
14
+ import { CalendarDate } from '@internationalized/date';
15
+
16
+
17
+ const internalModel = computed({
18
+ get: () => {
19
+
20
+ if (!modelValue.value) {
21
+ return null;
22
+ }
23
+
24
+ const date = new Date(modelValue.value);
25
+
26
+ return new CalendarDate(date.getFullYear(), date.getMonth() + 1, date.getDate());
27
+
28
+ },
29
+ set: v => {
30
+ modelValue.value = parseDate(String(v), 'YYYY-MM-DD');
31
+ },
32
+ });
33
+
34
+
35
+ const inputTitle = computed(() => {
36
+
37
+ if (!modelValue.value) {
38
+ return '';
39
+ }
40
+
41
+ return formatDate(modelValue.value, 'YYYY-MM-DD');
42
+
43
+ });
44
+
45
+ </script>
46
+
47
+
48
+ <template>
49
+ <u-popover>
50
+
51
+ <u-form-field v-bind="radPick(props.field, [ 'label' ])">
52
+ <u-input
53
+ class="w-full"
54
+ v-bind="radOmit(props.field, [ 'key', 'identifier', 'label' ])"
55
+ readonly
56
+ :model-value="inputTitle"
57
+ />
58
+ </u-form-field>
59
+
60
+ <template #content>
61
+
62
+ <u-calendar
63
+ class="ltr p-2 [&_th]:text-center!"
64
+ v-model="internalModel"
65
+ />
66
+
67
+ </template>
68
+
69
+ </u-popover>
70
+ </template>
@@ -0,0 +1,22 @@
1
+ <script setup>
2
+
3
+ /* interface */
4
+
5
+ const props = defineProps({
6
+ field: Object,
7
+ });
8
+
9
+ const modelValue = defineModel();
10
+
11
+ </script>
12
+
13
+
14
+ <template>
15
+ <u-form-field v-bind="radPick(props.field, [ 'label' ])">
16
+ <u-select
17
+ class="w-full"
18
+ v-bind="radOmit(props.field, [ 'key', 'identifier', 'label' ])"
19
+ v-model="modelValue"
20
+ />
21
+ </u-form-field>
22
+ </template>
@@ -0,0 +1,153 @@
1
+ <script setup>
2
+
3
+ /* interface */
4
+
5
+ const props = defineProps({
6
+ field: Object,
7
+ });
8
+
9
+ const modelValue = defineModel();
10
+
11
+
12
+ /* actions */
13
+
14
+ function handleAddItem() {
15
+ modelValue.value = [
16
+ ...(Array.isArray(modelValue.value) ? modelValue.value : []),
17
+ JSON.parse(JSON.stringify( props.field.itemBase ?? {} )),
18
+ ];
19
+ }
20
+
21
+ function handleDuplicateItem(index) {
22
+ modelValue.value = [
23
+ ...(modelValue.value.slice(0, index + 1)),
24
+ {
25
+ ...modelValue.value[index],
26
+ _id: undefined,
27
+ },
28
+ ...(modelValue.value.slice(index + 1)),
29
+ ];
30
+ }
31
+
32
+ function handleDeleteItem(index) {
33
+ modelValue.value = modelValue.value.filter((_, i) => i !== index);
34
+ }
35
+
36
+ function handleMoveItem(index, direction) {
37
+
38
+ const items = [ ...modelValue.value ];
39
+ const poppedItem = items.splice(index, 1)[0];
40
+
41
+ items.splice(index + direction, 0, poppedItem);
42
+
43
+ modelValue.value = items;
44
+
45
+ }
46
+
47
+ </script>
48
+
49
+
50
+ <template>
51
+ <div class="border border-default rounded">
52
+
53
+ <label class="text-sm flex items-center px-2 py-1 border-b border-default">
54
+
55
+ <span>
56
+ {{ props.field.label }}
57
+ </span>
58
+
59
+ <span class="text-xs ms-1">
60
+ ({{ (modelValue?.length > 0) ? (`${modelValue.length} Items`) : ('None') }})
61
+ </span>
62
+
63
+ <u-button
64
+ variant="soft"
65
+ size="xs"
66
+ icon="i-lucide-plus"
67
+ label="New Item"
68
+ class="ms-3"
69
+ @click="handleAddItem()"
70
+ />
71
+
72
+ </label>
73
+
74
+ <template v-if="!modelValue || !(modelValue.length > 0)">
75
+ <p class="text-xs text-center py-6">
76
+ No items added yet. Click on "New Item" to add one.
77
+ </p>
78
+ </template>
79
+
80
+ <div
81
+ v-else
82
+ class="p-2 grid gap-2 bg-stone-100"
83
+ :class="{
84
+ 'grid-cols-1': props.field.seriesColumns === 1 || !props.field.seriesColumns,
85
+ 'grid-cols-2': props.field.seriesColumns === 2,
86
+ 'grid-cols-3': props.field.seriesColumns === 3,
87
+ 'grid-cols-4': props.field.seriesColumns === 4,
88
+ 'grid-cols-5': props.field.seriesColumns === 5,
89
+ 'grid-cols-6': props.field.seriesColumns === 6,
90
+ }">
91
+ <div
92
+ v-for="(item, index) of modelValue" :key="index"
93
+ class="relative group">
94
+
95
+ <un-form
96
+ :target="item"
97
+ :fields="props.field.itemFields"
98
+ class="p-2 bg-default border border-default rounded"
99
+ />
100
+
101
+ <div
102
+ class="
103
+ absolute top-2 end-2
104
+ hidden
105
+ group-hover:flex group-hover:gap-1
106
+ ">
107
+
108
+ <u-tooltip text="Duplicate">
109
+ <u-button
110
+ variant="soft"
111
+ icon="i-lucide-copy"
112
+ size="xs"
113
+ @click="handleDuplicateItem(index)"
114
+ />
115
+ </u-tooltip>
116
+
117
+ <u-tooltip text="Move Back">
118
+ <u-button
119
+ v-if="index > 0"
120
+ variant="soft"
121
+ icon="i-lucide-chevron-left"
122
+ size="xs"
123
+ @click="handleMoveItem(index, -1)"
124
+ />
125
+ </u-tooltip>
126
+
127
+ <u-tooltip text="Move Forward">
128
+ <u-button
129
+ v-if="index < modelValue.length - 1"
130
+ variant="soft"
131
+ icon="i-lucide-chevron-right"
132
+ size="xs"
133
+ @click="handleMoveItem(index, 1)"
134
+ />
135
+ </u-tooltip>
136
+
137
+ <u-tooltip text="Delete">
138
+ <u-button
139
+ variant="soft"
140
+ color="error"
141
+ icon="i-lucide-trash"
142
+ size="xs"
143
+ @click="handleDeleteItem(index)"
144
+ />
145
+ </u-tooltip>
146
+
147
+ </div>
148
+
149
+ </div>
150
+ </div>
151
+
152
+ </div>
153
+ </template>
@@ -0,0 +1,22 @@
1
+ <script setup>
2
+
3
+ /* interface */
4
+
5
+ const props = defineProps({
6
+ field: Object,
7
+ });
8
+
9
+ const modelValue = defineModel();
10
+
11
+ </script>
12
+
13
+
14
+ <template>
15
+ <u-form-field v-bind="radPick(props.field, [ 'label' ])">
16
+ <u-input
17
+ class="w-full"
18
+ v-bind="radOmit(props.field, [ 'key', 'identifier', 'label' ])"
19
+ v-model="modelValue"
20
+ />
21
+ </u-form-field>
22
+ </template>
@@ -0,0 +1,36 @@
1
+ import { format as tempoFormat, parse } from '@formkit/tempo';
2
+
3
+
4
+ export function formatDate(timestamp: number | string, format = 'YYYY/MM/DD HH:mm', locale = 'en-US', timestampFormat?: string) {
5
+
6
+ if (!timestampFormat) {
7
+ try {
8
+ return tempoFormat(new Date(Number(timestamp)), format, locale);
9
+ }
10
+ catch {
11
+ return '';
12
+ }
13
+ }
14
+ else {
15
+ try {
16
+ return tempoFormat(
17
+ parse(String(timestamp), timestampFormat, locale),
18
+ format,
19
+ locale,
20
+ );
21
+ }
22
+ catch {
23
+ return '';
24
+ }
25
+ }
26
+
27
+ }
28
+
29
+ export function parseDate(date: string, format: string, locale = 'en-US') {
30
+ try {
31
+ return parse(date, format, locale).valueOf();
32
+ }
33
+ catch {
34
+ return 0;
35
+ }
36
+ }
@@ -0,0 +1,19 @@
1
+ import type { ButtonProps } from '@nuxt/ui';
2
+ import ChoicePickerDialog from '../dialogs/choice-picker-dialog.vue';
3
+
4
+
5
+ interface IOptions {
6
+ icon?: string;
7
+ title?: string;
8
+ subtitle?: string;
9
+ text?: string;
10
+ startButtons?: ( ButtonProps & { value?: string } )[];
11
+ endButtons?: ( ButtonProps & { value?: string } )[];
12
+ }
13
+
14
+ export function launchChoicePickerDialog(options: IOptions) {
15
+ return launchDialog({
16
+ component: ChoicePickerDialog,
17
+ props: options,
18
+ });
19
+ }
@@ -0,0 +1,11 @@
1
+
2
+
3
+ export async function launchDialog(options: { component: any, props: any }) {
4
+
5
+ const modal = useOverlay().create(options.component, {
6
+ destroyOnClose: true,
7
+ });
8
+
9
+ return await modal.open(options.props).result;
10
+
11
+ }
@@ -0,0 +1,21 @@
1
+ import type { ButtonProps } from '@nuxt/ui';
2
+ import FormPickerDialog from '../dialogs/form-picker-dialog.vue';
3
+
4
+
5
+ interface IOptions {
6
+ icon?: string;
7
+ title?: string;
8
+ subtitle?: string;
9
+ text?: string;
10
+ fields: any[];
11
+ initialForm?: any;
12
+ submitButton?: ButtonProps;
13
+ cancelButton?: ButtonProps;
14
+ }
15
+
16
+ export function launchFormPickerDialog(options: IOptions) {
17
+ return launchDialog({
18
+ component: FormPickerDialog,
19
+ props: options,
20
+ });
21
+ }
@@ -0,0 +1,26 @@
1
+
2
+
3
+ type IToast = Parameters<ReturnType<typeof useToast>['add']>['0'];
4
+ type ITypedToast = Omit<IToast, 'icon' | 'color'>;
5
+
6
+
7
+ export function toast(options: IToast) {
8
+ useToast().add(options);
9
+ }
10
+
11
+
12
+ export function toastSuccess(options: ITypedToast) {
13
+ toast({
14
+ icon: 'lucide:check',
15
+ color: 'success',
16
+ ...options,
17
+ });
18
+ }
19
+
20
+ export function toastError(options: ITypedToast) {
21
+ toast({
22
+ icon: 'lucide:circle-alert',
23
+ color: 'error',
24
+ ...options,
25
+ });
26
+ }
@@ -0,0 +1,6 @@
1
+ import { fileURLToPath } from 'node:url';
2
+
3
+
4
+ export function pathRelativeToBase(base: string, path: string) {
5
+ return fileURLToPath(new URL(path, base));
6
+ }
@@ -0,0 +1,23 @@
1
+ import { defineNuxtModule, addImports } from "@nuxt/kit";
2
+ import * as radash from "radash";
3
+
4
+
5
+ export default defineNuxtModule({
6
+ meta: {
7
+ name: "nuxt-radash",
8
+ },
9
+ setup() {
10
+ for (const name of Object.keys(radash)) {
11
+
12
+ const prefix = 'rad';
13
+ const as = `${prefix}${radash.pascal(name)}`;
14
+
15
+ addImports({
16
+ name,
17
+ as,
18
+ from: "radash",
19
+ });
20
+
21
+ }
22
+ },
23
+ });
package/nuxt.config.js ADDED
@@ -0,0 +1,26 @@
1
+ import { pathRelativeToBase } from './app/utils/path-relative';
2
+
3
+
4
+ export default defineNuxtConfig({
5
+
6
+ compatibilityDate: 'latest',
7
+ devtools: { enabled: false },
8
+
9
+ experimental: {
10
+ typedPages: true,
11
+ },
12
+
13
+ modules: [
14
+ '@vueuse/nuxt',
15
+ '@nuxt/ui',
16
+ ],
17
+
18
+ css: [
19
+ pathRelativeToBase(import.meta.url, './app/assets/css/main.css'),
20
+ ],
21
+
22
+ ui: {
23
+ colorMode: false,
24
+ },
25
+
26
+ });
package/package.json ADDED
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "nuxt-unified-ui",
3
+ "type": "module",
4
+ "version": "0.1.0",
5
+ "main": "./nuxt.config.js",
6
+ "files": [
7
+ "nuxt.config.js",
8
+ "package.json",
9
+ "app",
10
+ "layers",
11
+ "modules"
12
+ ],
13
+ "dependencies": {
14
+ "@formkit/tempo": "0.1.2",
15
+ "@iconify-json/lucide": "1.2.72",
16
+ "@nuxt/kit": "4.2.1",
17
+ "@nuxt/ui": "4.1.0",
18
+ "@vueuse/core": "14.0.0",
19
+ "@vueuse/nuxt": "14.0.0",
20
+ "radash": "12.1.1",
21
+ "unified-mongo-filter": "0.4.0"
22
+ },
23
+ "peerDependencies": {
24
+ "nuxt": ">4.0.0"
25
+ }
26
+ }