nuxt-ui-elements 0.1.8 → 0.1.11
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 +36 -30
- package/dist/module.json +1 -1
- package/dist/module.mjs +125 -13
- package/dist/runtime/components/DialogConfirm.d.vue.ts +49 -0
- package/dist/runtime/components/DialogConfirm.vue +170 -0
- package/dist/runtime/components/DialogConfirm.vue.d.ts +49 -0
- package/dist/runtime/composables/useDialog.d.ts +0 -0
- package/dist/runtime/composables/useDialog.js +25 -0
- package/dist/runtime/index.css +1 -0
- package/dist/runtime/types/tv.d.ts +64 -14
- package/package.json +14 -11
- package/dist/runtime/components/backgrounds/BackgroundAurora.d.vue.ts +0 -137
- package/dist/runtime/components/backgrounds/BackgroundAurora.vue +0 -145
- package/dist/runtime/components/backgrounds/BackgroundAurora.vue.d.ts +0 -137
- package/dist/runtime/components/backgrounds/BackgroundFlickeringGrid.d.vue.ts +0 -120
- package/dist/runtime/components/backgrounds/BackgroundFlickeringGrid.vue +0 -246
- package/dist/runtime/components/backgrounds/BackgroundFlickeringGrid.vue.d.ts +0 -120
- package/dist/runtime/composables/useColorResolver.d.ts +0 -40
- package/dist/runtime/composables/useColorResolver.js +0 -68
- package/dist/runtime/composables/useGradient.d.ts +0 -23
- package/dist/runtime/composables/useGradient.js +0 -37
- package/dist/runtime/composables/useThemeColors.d.ts +0 -69
- package/dist/runtime/composables/useThemeColors.js +0 -94
- package/dist/runtime/plugin.d.ts +0 -2
- package/dist/runtime/plugin.js +0 -4
- package/dist/runtime/themes/background-flickering-grid.d.ts +0 -7
- package/dist/runtime/themes/background-flickering-grid.js +0 -6
package/README.md
CHANGED
|
@@ -30,22 +30,32 @@ A collection of beautiful, animated UI components for Nuxt applications. Built w
|
|
|
30
30
|
|
|
31
31
|
## Quick Setup
|
|
32
32
|
|
|
33
|
-
Install the module to your Nuxt application
|
|
33
|
+
1. Install the module to your Nuxt application:
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
pnpm add nuxt-ui-elements
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Add the module to your `nuxt.config.ts`:
|
|
39
|
+
2. Add the module to your `nuxt.config.ts`:
|
|
40
40
|
|
|
41
41
|
```ts
|
|
42
42
|
export default defineNuxtConfig({
|
|
43
|
-
modules: [
|
|
43
|
+
modules: ["@nuxt/ui", "nuxt-ui-elements"],
|
|
44
44
|
})
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
3. Import the module's CSS in your main CSS file (e.g., `assets/css/main.css`):
|
|
48
|
+
|
|
49
|
+
```css
|
|
50
|
+
@import "tailwindcss";
|
|
51
|
+
@import "@nuxt/ui";
|
|
52
|
+
@import "nuxt-ui-elements";
|
|
53
|
+
```
|
|
54
|
+
|
|
47
55
|
That's it! You can now use Nuxt UI Elements in your Nuxt app ✨
|
|
48
56
|
|
|
57
|
+
> **Note:** This module requires `@nuxt/ui` v4.x as a peer dependency. The CSS import is required for Tailwind v4 to properly scan and generate styles from the component themes.
|
|
58
|
+
|
|
49
59
|
## Usage
|
|
50
60
|
|
|
51
61
|
### FlickeringGrid
|
|
@@ -61,8 +71,7 @@ A beautiful animated grid background component perfect for hero sections and lan
|
|
|
61
71
|
gradient-direction="left-right"
|
|
62
72
|
gradient-end-color="white"
|
|
63
73
|
:flicker-speed="0.2"
|
|
64
|
-
class="z-0"
|
|
65
|
-
/>
|
|
74
|
+
class="z-0" />
|
|
66
75
|
<div class="relative z-10">
|
|
67
76
|
<!-- Your content here -->
|
|
68
77
|
</div>
|
|
@@ -113,24 +122,23 @@ An animated button component with an elegant shimmer effect, perfect for call-to
|
|
|
113
122
|
shimmer-color="#60a5fa"
|
|
114
123
|
background="rgba(59, 130, 246, 1)"
|
|
115
124
|
:speed="2.5"
|
|
116
|
-
@click="handleClick"
|
|
117
|
-
/>
|
|
125
|
+
@click="handleClick" />
|
|
118
126
|
</template>
|
|
119
127
|
```
|
|
120
128
|
|
|
121
129
|
#### Props
|
|
122
130
|
|
|
123
|
-
| Prop
|
|
124
|
-
|
|
|
125
|
-
| `label`
|
|
126
|
-
| `shimmerColor`
|
|
127
|
-
| `shimmerSize`
|
|
128
|
-
| `speed`
|
|
129
|
-
| `background`
|
|
130
|
-
| `radius`
|
|
131
|
-
| `size`
|
|
132
|
-
| `class`
|
|
133
|
-
| `ui`
|
|
131
|
+
| Prop | Type | Default | Description |
|
|
132
|
+
| -------------- | -------- | -------------------- | ------------------------------------------- |
|
|
133
|
+
| `label` | `string` | - | Button label text |
|
|
134
|
+
| `shimmerColor` | `string` | `'#ffffff'` | Color of the shimmer effect |
|
|
135
|
+
| `shimmerSize` | `string` | `'0.05em'` | Size of the shimmer effect |
|
|
136
|
+
| `speed` | `number` | `3` | Animation speed in seconds (lower = faster) |
|
|
137
|
+
| `background` | `string` | `'rgba(0, 0, 0, 1)'` | Background color of the button |
|
|
138
|
+
| `radius` | `string` | `'100px'` | Border radius of the button |
|
|
139
|
+
| `size` | `string` | `'md'` | Button size: `xs`, `sm`, `md`, `lg`, `xl` |
|
|
140
|
+
| `class` | `string` | - | Additional CSS classes |
|
|
141
|
+
| `ui` | `object` | - | UI slot customization for advanced styling |
|
|
134
142
|
|
|
135
143
|
#### Slots
|
|
136
144
|
|
|
@@ -145,20 +153,19 @@ An animated button component with an elegant shimmer effect, perfect for call-to
|
|
|
145
153
|
#### Examples
|
|
146
154
|
|
|
147
155
|
**Basic Usage:**
|
|
156
|
+
|
|
148
157
|
```vue
|
|
149
158
|
<UEButtonShimmer label="Click Me" />
|
|
150
159
|
```
|
|
151
160
|
|
|
152
161
|
**With Custom Colors:**
|
|
162
|
+
|
|
153
163
|
```vue
|
|
154
|
-
<UEButtonShimmer
|
|
155
|
-
label="Primary Action"
|
|
156
|
-
shimmer-color="#a78bfa"
|
|
157
|
-
background="rgba(139, 92, 246, 1)"
|
|
158
|
-
/>
|
|
164
|
+
<UEButtonShimmer label="Primary Action" shimmer-color="#a78bfa" background="rgba(139, 92, 246, 1)" />
|
|
159
165
|
```
|
|
160
166
|
|
|
161
167
|
**Different Sizes:**
|
|
168
|
+
|
|
162
169
|
```vue
|
|
163
170
|
<UEButtonShimmer label="Small" size="sm" />
|
|
164
171
|
<UEButtonShimmer label="Medium" size="md" />
|
|
@@ -167,6 +174,7 @@ An animated button component with an elegant shimmer effect, perfect for call-to
|
|
|
167
174
|
```
|
|
168
175
|
|
|
169
176
|
**With Slots:**
|
|
177
|
+
|
|
170
178
|
```vue
|
|
171
179
|
<UEButtonShimmer>
|
|
172
180
|
<template #leading>
|
|
@@ -180,11 +188,9 @@ An animated button component with an elegant shimmer effect, perfect for call-to
|
|
|
180
188
|
```
|
|
181
189
|
|
|
182
190
|
**Fast Animation:**
|
|
191
|
+
|
|
183
192
|
```vue
|
|
184
|
-
<UEButtonShimmer
|
|
185
|
-
label="Fast Shimmer"
|
|
186
|
-
:speed="1.5"
|
|
187
|
-
/>
|
|
193
|
+
<UEButtonShimmer label="Fast Shimmer" :speed="1.5" />
|
|
188
194
|
```
|
|
189
195
|
|
|
190
196
|
## Configuration
|
|
@@ -193,10 +199,10 @@ You can customize the component prefix in your `nuxt.config.ts`:
|
|
|
193
199
|
|
|
194
200
|
```ts
|
|
195
201
|
export default defineNuxtConfig({
|
|
196
|
-
modules: [
|
|
202
|
+
modules: ["nuxt-ui-elements"],
|
|
197
203
|
uiElements: {
|
|
198
|
-
prefix:
|
|
199
|
-
}
|
|
204
|
+
prefix: "Custom", // Components will be named CustomFlickeringGrid
|
|
205
|
+
},
|
|
200
206
|
})
|
|
201
207
|
```
|
|
202
208
|
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,115 @@
|
|
|
1
|
-
import { defineNuxtModule, createResolver,
|
|
1
|
+
import { addTemplate, defineNuxtModule, createResolver, addImportsDir } from '@nuxt/kit';
|
|
2
|
+
import { kebabCase } from 'scule';
|
|
3
|
+
|
|
4
|
+
const dialogConfirm = (options) => ({
|
|
5
|
+
slots: {
|
|
6
|
+
content: "divide-y-0 bg-default ring-0",
|
|
7
|
+
header: "relative flex items-center gap-1.5 p-4 sm:px-6 min-h-16",
|
|
8
|
+
icon: "shrink-0 mt-1 size-6",
|
|
9
|
+
title: "text-lg font-semibold",
|
|
10
|
+
description: "text-sm opacity-90",
|
|
11
|
+
body: "p-2 sm:p-5 sm:pt-0",
|
|
12
|
+
footer: "justify-end gap-3",
|
|
13
|
+
close: ""
|
|
14
|
+
},
|
|
15
|
+
variants: {
|
|
16
|
+
size: {
|
|
17
|
+
sm: {
|
|
18
|
+
content: "sm:max-w-md"
|
|
19
|
+
},
|
|
20
|
+
md: {
|
|
21
|
+
content: "sm:max-w-3xl"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
color: {
|
|
25
|
+
...Object.fromEntries((options.theme.colors || []).map((color) => [color, ""])),
|
|
26
|
+
neutral: ""
|
|
27
|
+
},
|
|
28
|
+
variant: {
|
|
29
|
+
solid: "",
|
|
30
|
+
outline: ""
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
compoundVariants: [
|
|
34
|
+
// Solid variants - dynamically generated for all theme colors
|
|
35
|
+
...(options.theme.colors || []).map((color) => ({
|
|
36
|
+
color,
|
|
37
|
+
variant: "solid",
|
|
38
|
+
class: {
|
|
39
|
+
content: `bg-${color} text-inverted`,
|
|
40
|
+
close: "!text-white hover:bg-white/10 active:bg-white/20"
|
|
41
|
+
}
|
|
42
|
+
})),
|
|
43
|
+
{
|
|
44
|
+
color: "neutral",
|
|
45
|
+
variant: "solid",
|
|
46
|
+
class: {
|
|
47
|
+
content: "text-inverted bg-inverted",
|
|
48
|
+
close: "!text-white hover:bg-white/10 active:bg-white/20"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
// Outline variants - dynamically generated for all theme colors
|
|
52
|
+
...(options.theme.colors || []).map((color) => ({
|
|
53
|
+
color,
|
|
54
|
+
variant: "outline",
|
|
55
|
+
class: {
|
|
56
|
+
content: `bg-gradient-to-b from-${color}/5 to-${color}/5 bg-default text-${color} !ring !ring-inset !ring-${color}`
|
|
57
|
+
}
|
|
58
|
+
})),
|
|
59
|
+
{
|
|
60
|
+
color: "neutral",
|
|
61
|
+
variant: "outline",
|
|
62
|
+
class: {
|
|
63
|
+
content: "text-highlighted bg-elevated !ring !ring-inset !ring-accented"
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
],
|
|
67
|
+
defaultVariants: {
|
|
68
|
+
color: "primary",
|
|
69
|
+
variant: "solid",
|
|
70
|
+
size: "sm"
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
const theme = {
|
|
75
|
+
__proto__: null,
|
|
76
|
+
dialogConfirm: dialogConfirm
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
function addTemplates(options, _nuxt) {
|
|
80
|
+
const templates = [];
|
|
81
|
+
function writeThemeTemplate(themeModules) {
|
|
82
|
+
for (const component in themeModules) {
|
|
83
|
+
templates.push({
|
|
84
|
+
filename: `ui-elements/${kebabCase(component)}.ts`,
|
|
85
|
+
write: true,
|
|
86
|
+
getContents: () => {
|
|
87
|
+
const template = themeModules[component];
|
|
88
|
+
const result = typeof template === "function" ? template(options) : template;
|
|
89
|
+
const variants = Object.entries(result.variants || {}).filter(([_, values]) => {
|
|
90
|
+
const keys = Object.keys(values);
|
|
91
|
+
return keys.some((key) => key !== "true" && key !== "false");
|
|
92
|
+
}).map(([key]) => key);
|
|
93
|
+
let json = JSON.stringify(result, null, 2);
|
|
94
|
+
for (const variant of variants) {
|
|
95
|
+
json = json.replace(new RegExp(`("${variant}": "[^"]+")`, "g"), `$1 as typeof ${variant}[number]`);
|
|
96
|
+
json = json.replace(new RegExp(`("${variant}": \\[\\s*)((?:"[^"]+",?\\s*)+)(\\])`, "g"), (_, before, match, after) => {
|
|
97
|
+
const replaced = match.replace(/("[^"]+")/g, `$1 as typeof ${variant}[number]`);
|
|
98
|
+
return `${before}${replaced}${after}`;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const variantDeclarations = variants.filter((variant) => json.includes(`as typeof ${variant}`)).map((variant) => {
|
|
102
|
+
const keys = Object.keys(result.variants[variant]);
|
|
103
|
+
return `const ${variant} = ${JSON.stringify(keys, null, 2)} as const`;
|
|
104
|
+
});
|
|
105
|
+
return [...variantDeclarations, `export default ${json}`].join("\n\n");
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
writeThemeTemplate(theme);
|
|
111
|
+
templates.forEach((template) => addTemplate(template));
|
|
112
|
+
}
|
|
2
113
|
|
|
3
114
|
const module$1 = defineNuxtModule({
|
|
4
115
|
meta: {
|
|
@@ -9,20 +120,21 @@ const module$1 = defineNuxtModule({
|
|
|
9
120
|
prefix: "UE"
|
|
10
121
|
},
|
|
11
122
|
moduleDependencies: {
|
|
12
|
-
"
|
|
13
|
-
"@nuxtjs/color-mode": {}
|
|
123
|
+
"@nuxt/ui": {}
|
|
14
124
|
},
|
|
15
|
-
setup(options,
|
|
125
|
+
setup(options, nuxt) {
|
|
16
126
|
const resolver = createResolver(import.meta.url);
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
127
|
+
const uiOptions = nuxt.options.ui;
|
|
128
|
+
const themeColors = uiOptions?.theme?.colors || ["primary", "secondary", "success", "info", "warning", "error"];
|
|
129
|
+
const optionsWithTheme = {
|
|
130
|
+
...options,
|
|
131
|
+
theme: {
|
|
132
|
+
colors: themeColors
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
addTemplates(optionsWithTheme);
|
|
136
|
+
nuxt.options.css.push(resolver.resolve("./runtime/index.css"));
|
|
137
|
+
addImportsDir(resolver.resolve("./runtime/composables"));
|
|
26
138
|
}
|
|
27
139
|
});
|
|
28
140
|
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AppConfig } from "@nuxt/schema";
|
|
2
|
+
import theme from "#build/ui-elements/dialog-confirm";
|
|
3
|
+
import type { ComponentConfig } from "../types/tv.js";
|
|
4
|
+
type DialogConfirm = ComponentConfig<typeof theme, AppConfig, "dialogConfirm">;
|
|
5
|
+
export interface DialogConfirmEmits {
|
|
6
|
+
"update:open": [value: boolean];
|
|
7
|
+
close: [value?: any];
|
|
8
|
+
"after:leave": [];
|
|
9
|
+
}
|
|
10
|
+
export interface DialogConfirmSlots {
|
|
11
|
+
title(props?: {}): any;
|
|
12
|
+
description(props?: {}): any;
|
|
13
|
+
actions(props?: {}): any;
|
|
14
|
+
}
|
|
15
|
+
export interface DialogConfirmProps {
|
|
16
|
+
title: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
icon?: boolean;
|
|
19
|
+
confirmLabel?: string;
|
|
20
|
+
dismissLabel?: string;
|
|
21
|
+
close?: boolean;
|
|
22
|
+
color?: DialogConfirm["variants"]["color"];
|
|
23
|
+
variant?: DialogConfirm["variants"]["variant"];
|
|
24
|
+
onConfirm?: (() => void) | (() => Promise<void>);
|
|
25
|
+
onDismiss?: () => void;
|
|
26
|
+
ui?: Partial<DialogConfirm["slots"]>;
|
|
27
|
+
}
|
|
28
|
+
declare const _default: typeof __VLS_export;
|
|
29
|
+
export default _default;
|
|
30
|
+
declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<DialogConfirmProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
|
+
close: (value?: any) => any;
|
|
32
|
+
"update:open": (value: boolean) => any;
|
|
33
|
+
"after:leave": () => any;
|
|
34
|
+
}, string, import("vue").PublicProps, Readonly<DialogConfirmProps> & Readonly<{
|
|
35
|
+
onClose?: ((value?: any) => any) | undefined;
|
|
36
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
37
|
+
"onAfter:leave"?: (() => any) | undefined;
|
|
38
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
|
|
39
|
+
title?: (props: {}) => any;
|
|
40
|
+
} & {
|
|
41
|
+
description?: (props: {}) => any;
|
|
42
|
+
} & {
|
|
43
|
+
actions?: (props: {}) => any;
|
|
44
|
+
}>;
|
|
45
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
46
|
+
new (): {
|
|
47
|
+
$slots: S;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import theme from "#build/ui-elements/dialog-confirm";
|
|
3
|
+
import { computed, ref } from "vue";
|
|
4
|
+
import { DialogTitle, DialogDescription } from "reka-ui";
|
|
5
|
+
import { tv } from "../utils/tv";
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<script setup>
|
|
9
|
+
const {
|
|
10
|
+
title,
|
|
11
|
+
description = "",
|
|
12
|
+
icon = true,
|
|
13
|
+
confirmLabel = "Yes",
|
|
14
|
+
dismissLabel = "No",
|
|
15
|
+
close = false,
|
|
16
|
+
color = "neutral",
|
|
17
|
+
variant = "solid",
|
|
18
|
+
onConfirm,
|
|
19
|
+
onDismiss,
|
|
20
|
+
ui: uiProps
|
|
21
|
+
} = defineProps({
|
|
22
|
+
title: { type: String, required: true },
|
|
23
|
+
description: { type: String, required: false },
|
|
24
|
+
icon: { type: Boolean, required: false },
|
|
25
|
+
confirmLabel: { type: String, required: false },
|
|
26
|
+
dismissLabel: { type: String, required: false },
|
|
27
|
+
close: { type: Boolean, required: false },
|
|
28
|
+
color: { type: null, required: false },
|
|
29
|
+
variant: { type: null, required: false },
|
|
30
|
+
onConfirm: { type: Function, required: false },
|
|
31
|
+
onDismiss: { type: Function, required: false },
|
|
32
|
+
ui: { type: Object, required: false }
|
|
33
|
+
});
|
|
34
|
+
const emits = defineEmits(["update:open", "close", "after:leave"]);
|
|
35
|
+
const isLoading = ref(false);
|
|
36
|
+
const isComplete = ref(false);
|
|
37
|
+
const isError = ref(false);
|
|
38
|
+
const ui = computed(
|
|
39
|
+
() => tv({
|
|
40
|
+
extend: tv(theme)
|
|
41
|
+
})({
|
|
42
|
+
size: "sm",
|
|
43
|
+
color,
|
|
44
|
+
variant
|
|
45
|
+
})
|
|
46
|
+
);
|
|
47
|
+
const colorIconMap = {
|
|
48
|
+
primary: "i-lucide-info",
|
|
49
|
+
secondary: "i-lucide-info",
|
|
50
|
+
success: "i-lucide-circle-check",
|
|
51
|
+
info: "i-lucide-info",
|
|
52
|
+
warning: "i-lucide-triangle-alert",
|
|
53
|
+
error: "i-lucide-circle-x",
|
|
54
|
+
neutral: "i-lucide-info"
|
|
55
|
+
};
|
|
56
|
+
const dialogIcon = computed(() => {
|
|
57
|
+
if (icon === false) return void 0;
|
|
58
|
+
return colorIconMap[color];
|
|
59
|
+
});
|
|
60
|
+
const closeButtonProps = computed(() => ({
|
|
61
|
+
icon: "i-lucide-x",
|
|
62
|
+
size: "md",
|
|
63
|
+
color: "neutral",
|
|
64
|
+
variant: "ghost"
|
|
65
|
+
}));
|
|
66
|
+
const confirmHandler = async () => {
|
|
67
|
+
if (!onConfirm) {
|
|
68
|
+
emits("close");
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const result = onConfirm();
|
|
73
|
+
const isAsync = result instanceof Promise;
|
|
74
|
+
if (isAsync) {
|
|
75
|
+
isLoading.value = true;
|
|
76
|
+
await result;
|
|
77
|
+
isLoading.value = false;
|
|
78
|
+
isComplete.value = true;
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
emits("close");
|
|
81
|
+
}, 800);
|
|
82
|
+
} else {
|
|
83
|
+
emits("close");
|
|
84
|
+
}
|
|
85
|
+
} catch (error) {
|
|
86
|
+
isLoading.value = false;
|
|
87
|
+
isError.value = true;
|
|
88
|
+
console.error("Dialog confirm error:", error);
|
|
89
|
+
setTimeout(() => {
|
|
90
|
+
isError.value = false;
|
|
91
|
+
}, 2e3);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const dismiss = () => {
|
|
95
|
+
if (onDismiss) {
|
|
96
|
+
onDismiss();
|
|
97
|
+
}
|
|
98
|
+
emits("close");
|
|
99
|
+
};
|
|
100
|
+
const handleClose = () => {
|
|
101
|
+
dismiss();
|
|
102
|
+
};
|
|
103
|
+
</script>
|
|
104
|
+
|
|
105
|
+
<template>
|
|
106
|
+
<UModal
|
|
107
|
+
:dismissible="false"
|
|
108
|
+
:close="false"
|
|
109
|
+
:title="title"
|
|
110
|
+
:description="description"
|
|
111
|
+
:ui="{
|
|
112
|
+
content: ui.content({ class: uiProps?.content }),
|
|
113
|
+
header: ui.header({ class: uiProps?.header }),
|
|
114
|
+
body: ui.body({ class: uiProps?.body }),
|
|
115
|
+
footer: ui.footer({ class: uiProps?.footer })
|
|
116
|
+
}"
|
|
117
|
+
@close="emits('close', $event)"
|
|
118
|
+
@after:leave="emits('after:leave')">
|
|
119
|
+
<template #header>
|
|
120
|
+
<div class="relative w-full flex items-start gap-3">
|
|
121
|
+
<UIcon v-if="dialogIcon" :name="dialogIcon" data-slot="icon" :class="ui.icon({ class: uiProps?.icon })" />
|
|
122
|
+
<div class="flex-1 min-w-0">
|
|
123
|
+
<DialogTitle v-if="title" data-slot="title" :class="ui.title({ class: uiProps?.title })">
|
|
124
|
+
<slot name="title">
|
|
125
|
+
{{ title }}
|
|
126
|
+
</slot>
|
|
127
|
+
</DialogTitle>
|
|
128
|
+
|
|
129
|
+
<DialogDescription v-if="description" :class="ui.description({ class: uiProps?.description })">
|
|
130
|
+
<slot name="description">
|
|
131
|
+
{{ description }}
|
|
132
|
+
</slot>
|
|
133
|
+
</DialogDescription>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<UButton
|
|
137
|
+
v-if="close"
|
|
138
|
+
v-bind="closeButtonProps"
|
|
139
|
+
data-slot="close"
|
|
140
|
+
:class="ui.close({ class: uiProps?.close })"
|
|
141
|
+
@click="handleClose" />
|
|
142
|
+
</div>
|
|
143
|
+
</template>
|
|
144
|
+
|
|
145
|
+
<template #footer>
|
|
146
|
+
<slot name="actions">
|
|
147
|
+
<!-- Dismiss button (No) - hidden when loading, complete, or error -->
|
|
148
|
+
<UButton
|
|
149
|
+
v-if="dismissLabel && !isLoading && !isComplete && !isError"
|
|
150
|
+
:label="dismissLabel"
|
|
151
|
+
size="lg"
|
|
152
|
+
:color="color"
|
|
153
|
+
variant="solid"
|
|
154
|
+
@click="dismiss" />
|
|
155
|
+
|
|
156
|
+
<!-- Confirm button (Yes) - shows loading/complete/error states -->
|
|
157
|
+
<UButton
|
|
158
|
+
:label="isError ? 'Error' : isComplete ? 'Complete' : confirmLabel"
|
|
159
|
+
size="lg"
|
|
160
|
+
color="neutral"
|
|
161
|
+
variant="outline"
|
|
162
|
+
:loading="isLoading"
|
|
163
|
+
:disabled="isComplete"
|
|
164
|
+
:leading-icon="isError ? 'i-lucide-circle-x' : isComplete ? 'i-lucide-check' : void 0"
|
|
165
|
+
:class="{ 'animate-shake': isError }"
|
|
166
|
+
@click="confirmHandler" />
|
|
167
|
+
</slot>
|
|
168
|
+
</template>
|
|
169
|
+
</UModal>
|
|
170
|
+
</template>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { AppConfig } from "@nuxt/schema";
|
|
2
|
+
import theme from "#build/ui-elements/dialog-confirm";
|
|
3
|
+
import type { ComponentConfig } from "../types/tv.js";
|
|
4
|
+
type DialogConfirm = ComponentConfig<typeof theme, AppConfig, "dialogConfirm">;
|
|
5
|
+
export interface DialogConfirmEmits {
|
|
6
|
+
"update:open": [value: boolean];
|
|
7
|
+
close: [value?: any];
|
|
8
|
+
"after:leave": [];
|
|
9
|
+
}
|
|
10
|
+
export interface DialogConfirmSlots {
|
|
11
|
+
title(props?: {}): any;
|
|
12
|
+
description(props?: {}): any;
|
|
13
|
+
actions(props?: {}): any;
|
|
14
|
+
}
|
|
15
|
+
export interface DialogConfirmProps {
|
|
16
|
+
title: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
icon?: boolean;
|
|
19
|
+
confirmLabel?: string;
|
|
20
|
+
dismissLabel?: string;
|
|
21
|
+
close?: boolean;
|
|
22
|
+
color?: DialogConfirm["variants"]["color"];
|
|
23
|
+
variant?: DialogConfirm["variants"]["variant"];
|
|
24
|
+
onConfirm?: (() => void) | (() => Promise<void>);
|
|
25
|
+
onDismiss?: () => void;
|
|
26
|
+
ui?: Partial<DialogConfirm["slots"]>;
|
|
27
|
+
}
|
|
28
|
+
declare const _default: typeof __VLS_export;
|
|
29
|
+
export default _default;
|
|
30
|
+
declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<DialogConfirmProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
31
|
+
close: (value?: any) => any;
|
|
32
|
+
"update:open": (value: boolean) => any;
|
|
33
|
+
"after:leave": () => any;
|
|
34
|
+
}, string, import("vue").PublicProps, Readonly<DialogConfirmProps> & Readonly<{
|
|
35
|
+
onClose?: ((value?: any) => any) | undefined;
|
|
36
|
+
"onUpdate:open"?: ((value: boolean) => any) | undefined;
|
|
37
|
+
"onAfter:leave"?: (() => any) | undefined;
|
|
38
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
|
|
39
|
+
title?: (props: {}) => any;
|
|
40
|
+
} & {
|
|
41
|
+
description?: (props: {}) => any;
|
|
42
|
+
} & {
|
|
43
|
+
actions?: (props: {}) => any;
|
|
44
|
+
}>;
|
|
45
|
+
type __VLS_WithSlots<T, S> = T & {
|
|
46
|
+
new (): {
|
|
47
|
+
$slots: S;
|
|
48
|
+
};
|
|
49
|
+
};
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import DialogConfirm from "../components/DialogConfirm.vue";
|
|
2
|
+
import { useOverlay } from "#imports";
|
|
3
|
+
import { navigateTo } from "#app";
|
|
4
|
+
export const useDialog = () => {
|
|
5
|
+
const overlay = useOverlay();
|
|
6
|
+
const confirm = (options) => {
|
|
7
|
+
const modal = overlay.create(DialogConfirm, {
|
|
8
|
+
destroyOnClose: true,
|
|
9
|
+
props: options
|
|
10
|
+
});
|
|
11
|
+
modal.open();
|
|
12
|
+
return modal;
|
|
13
|
+
};
|
|
14
|
+
const confirmNavigate = (path, options) => {
|
|
15
|
+
confirm({
|
|
16
|
+
title: "Leave this page?",
|
|
17
|
+
description: "Are you sure you want to navigate away? Unsaved changes will be lost.",
|
|
18
|
+
...options,
|
|
19
|
+
onConfirm: () => {
|
|
20
|
+
navigateTo(path);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
};
|
|
24
|
+
return { confirm, confirmNavigate };
|
|
25
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@source "../theme";@source "./components";@keyframes shake{0%,to{transform:translateX(0)}10%,30%,50%,70%,90%{transform:translateX(-4px)}20%,40%,60%,80%{transform:translateX(4px)}}.animate-shake{animation:shake .5s ease-in-out}
|
|
@@ -1,14 +1,64 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
slots:
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import type { ClassValue, TVVariants, TVCompoundVariants, TVDefaultVariants } from "tailwind-variants";
|
|
2
|
+
/**
|
|
3
|
+
* Defines the AppConfig object based on the tailwind-variants configuration.
|
|
4
|
+
*/
|
|
5
|
+
export type TVConfig<T extends Record<string, any>> = {
|
|
6
|
+
[P in keyof T]?: {
|
|
7
|
+
[K in keyof T[P] as K extends "base" | "slots" | "variants" | "defaultVariants" ? K : never]?: K extends "base" ? ClassValue : K extends "slots" ? {
|
|
8
|
+
[S in keyof T[P]["slots"]]?: ClassValue;
|
|
9
|
+
} : K extends "variants" ? TVVariants<T[P]["slots"], ClassValue, WidenVariantsValues<T[P]["variants"]>> : K extends "defaultVariants" ? TVDefaultVariants<WidenVariantsValues<T[P]["variants"]>, T[P]["slots"], object, undefined> : never;
|
|
10
|
+
};
|
|
11
|
+
} & {
|
|
12
|
+
[P in keyof T]?: {
|
|
13
|
+
compoundVariants?: TVCompoundVariants<WidenVariantsValues<T[P]["variants"]>, T[P]["slots"], ClassValue, object, undefined>;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
type WidenVariantsValues<V extends Record<string, any> | undefined> = V extends Record<string, any> ? V & {
|
|
17
|
+
[K in keyof V]: V[K] extends Record<string, any> ? V[K] & Record<string & {}, any> : V[K];
|
|
18
|
+
} : V;
|
|
19
|
+
/**
|
|
20
|
+
* Utility type to flatten intersection types for better IDE hover information.
|
|
21
|
+
* @template T The type to flatten.
|
|
22
|
+
*/
|
|
23
|
+
type Id<T> = {} & {
|
|
24
|
+
[P in keyof T]: T[P];
|
|
25
|
+
};
|
|
26
|
+
type ComponentVariants<T extends {
|
|
27
|
+
variants?: Record<string, Record<string, any>>;
|
|
28
|
+
}> = {
|
|
29
|
+
[K in keyof T["variants"]]: keyof T["variants"][K];
|
|
30
|
+
};
|
|
31
|
+
type ComponentSlots<T extends {
|
|
32
|
+
slots?: Record<string, any>;
|
|
33
|
+
}> = Id<{
|
|
34
|
+
[K in keyof T["slots"]]?: ClassValue;
|
|
35
|
+
}>;
|
|
36
|
+
type ComponentUI<T extends {
|
|
37
|
+
slots?: Record<string, any>;
|
|
38
|
+
}> = Id<{
|
|
39
|
+
[K in keyof Required<T["slots"]>]: (props?: Record<string, any>) => string;
|
|
40
|
+
}>;
|
|
41
|
+
type GetComponentAppConfig<A, U extends string, K extends string> = A extends Record<U, Record<K, any>> ? A[U][K] : {};
|
|
42
|
+
type ComponentAppConfig<T, A extends Record<string, any>, K extends string, U extends string = "ui" | "uiElements"> = A & (U extends "uiElements" ? {
|
|
43
|
+
uiElements?: {
|
|
44
|
+
[k in K]?: Partial<T>;
|
|
45
|
+
};
|
|
46
|
+
} : {
|
|
47
|
+
[key in Exclude<U, "uiElements">]?: {
|
|
48
|
+
[k in K]?: Partial<T>;
|
|
49
|
+
};
|
|
50
|
+
});
|
|
51
|
+
/**
|
|
52
|
+
* Defines the configuration shape expected for a component.
|
|
53
|
+
* @template T The component's theme imported from `#build/ui-elements/*`.
|
|
54
|
+
* @template A The base AppConfig type from `@nuxt/schema`.
|
|
55
|
+
* @template K The key identifying the component (e.g., 'dialogConfirm').
|
|
56
|
+
* @template U The top-level key in AppConfig ('ui' or 'uiElements').
|
|
57
|
+
*/
|
|
58
|
+
export type ComponentConfig<T extends Record<string, any>, A extends Record<string, any>, K extends string, U extends "ui" | "uiElements" = "uiElements"> = {
|
|
59
|
+
AppConfig: ComponentAppConfig<T, A, K, U>;
|
|
60
|
+
variants: ComponentVariants<T & GetComponentAppConfig<A, U, K>>;
|
|
61
|
+
slots: ComponentSlots<T>;
|
|
62
|
+
ui: ComponentUI<T>;
|
|
63
|
+
};
|
|
64
|
+
export {};
|