@zantopia/zephyr-widget 0.4.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.
@@ -0,0 +1,140 @@
1
+ <!--
2
+ 🦊 @zephyr/widget/vue — Vue 3 component wrapper
3
+
4
+ Usage:
5
+ <script setup>
6
+ import ZephyrWidget from '@zephyr/widget/vue';
7
+ </script>
8
+
9
+ <template>
10
+ <ZephyrWidget
11
+ server="https://your-zephyr-server"
12
+ persona="minimal"
13
+ theme="dark"
14
+ position="bottom-right"
15
+ accent-color="#ff6b35"
16
+ @message="onMessage"
17
+ />
18
+ </template>
19
+
20
+ Or inline:
21
+ <ZephyrWidget server="..." :inline="true" style="height: 500px" />
22
+ -->
23
+
24
+ <template>
25
+ <div v-if="inline" ref="containerEl" class="zephyr-vue-container" :style="containerStyle">
26
+ <!-- Widget mounts here in inline mode -->
27
+ </div>
28
+ <!-- In floating mode, widget is appended to body — no template needed -->
29
+ </template>
30
+
31
+ <script setup>
32
+ import { ref, onMounted, onUnmounted, watch, computed } from "vue";
33
+
34
+ const props = defineProps({
35
+ /** Zephyr backend server URL (required) */
36
+ server: { type: String, required: true },
37
+ /** API key */
38
+ apiKey: { type: String, default: "" },
39
+ /** Persona: "mascot" | "spirit" | "minimal" | "futuristic" | custom URL */
40
+ persona: { type: String, default: "minimal" },
41
+ /** Theme */
42
+ theme: { type: String, default: "dark" },
43
+ /** Position */
44
+ position: { type: String, default: "bottom-right" },
45
+ /** Size */
46
+ size: { type: String, default: "md" },
47
+ /** Language */
48
+ language: { type: String, default: "fr" },
49
+ /** Custom greeting */
50
+ greeting: { type: String, default: null },
51
+ /** Placeholder */
52
+ placeholder: { type: String, default: null },
53
+ /** Accent color */
54
+ accentColor: { type: String, default: "#ff6b35" },
55
+ /** z-index */
56
+ zIndex: { type: Number, default: 99999 },
57
+ /** Start open */
58
+ open: { type: Boolean, default: false },
59
+ /** Show badge */
60
+ showBadge: { type: Boolean, default: true },
61
+ /** Features */
62
+ features: { type: Array, default: () => ["chat", "guide", "search"] },
63
+ /** Inline mode — renders inside container instead of floating */
64
+ inline: { type: Boolean, default: false },
65
+ /** Custom CSS */
66
+ customCSS: { type: String, default: "" },
67
+ });
68
+
69
+ const emit = defineEmits(["ready", "message", "error", "toggle"]);
70
+
71
+ const containerEl = ref(null);
72
+ let widget = null;
73
+ let WidgetClass = null;
74
+
75
+ const containerStyle = computed(() =>
76
+ props.inline ? { width: "100%", height: "100%" } : {}
77
+ );
78
+
79
+ async function loadWidget() {
80
+ if (WidgetClass) return;
81
+ try {
82
+ const mod = await import("../zephyr-widget.js");
83
+ WidgetClass = mod.Widget || mod.default?.Widget;
84
+ } catch (e) {
85
+ console.error("[ZephyrWidget] Could not load zephyr-widget.js", e);
86
+ }
87
+ }
88
+
89
+ onMounted(async () => {
90
+ await loadWidget();
91
+ if (!WidgetClass) return;
92
+
93
+ widget = new WidgetClass({
94
+ server: props.server,
95
+ apiKey: props.apiKey,
96
+ persona: props.persona,
97
+ theme: props.theme,
98
+ position: props.position,
99
+ size: props.size,
100
+ language: props.language,
101
+ greeting: props.greeting,
102
+ placeholder: props.placeholder,
103
+ accentColor: props.accentColor,
104
+ zIndex: props.zIndex,
105
+ open: props.open,
106
+ showBadge: props.showBadge,
107
+ features: props.features,
108
+ customCSS: props.customCSS,
109
+ containerSelector: props.inline ? null : null,
110
+ onReady: (w) => emit("ready", w),
111
+ onMessage: (msg) => emit("message", msg),
112
+ onError: (err) => emit("error", err),
113
+ onToggle: (isOpen) => emit("toggle", isOpen),
114
+ });
115
+
116
+ if (props.inline && containerEl.value) {
117
+ widget.mount(containerEl.value);
118
+ } else {
119
+ widget.mount();
120
+ }
121
+ });
122
+
123
+ onUnmounted(() => {
124
+ if (widget) widget.destroy();
125
+ });
126
+
127
+ // Reactivity
128
+ watch(() => props.theme, (val) => widget?.setTheme(val));
129
+ watch(() => props.persona, (val) => widget?.setPersona(val));
130
+ watch(() => props.accentColor, (val) => widget?.setAccentColor(val));
131
+
132
+ // Expose methods
133
+ defineExpose({
134
+ send: (text) => widget?.send(text),
135
+ open: () => widget?.open(),
136
+ close: () => widget?.close(),
137
+ toggle: () => widget?.toggle(),
138
+ getInstance: () => widget,
139
+ });
140
+ </script>