@vasakgroup/vue-libvasak 0.1.0 → 0.2.1

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/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@vasakgroup/vue-libvasak",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Vue componenets for VSK Applications",
5
5
  "main": "./dist/vue-libvasak.umd.js",
6
6
  "module": "./dist/vue-libvasak.es.js",
7
- "types": "./dist/index.d.ts",
7
+ "types": "src/types/vue-libvasak.d.ts",
8
8
  "files": [
9
9
  "dist",
10
10
  "src"
@@ -42,18 +42,18 @@
42
42
  },
43
43
  "license": "GPLv3",
44
44
  "dependencies": {
45
- "@tauri-apps/api": "^2.8.0",
45
+ "@tauri-apps/api": "^2.10.1",
46
46
  "@vasakgroup/plugin-vicons": "^2.0.0",
47
- "vue": "^3.5.20",
48
- "vue-router": "^4.5.1"
47
+ "vue": "^3.5.28",
48
+ "vue-router": "^5.0.3"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "vue": "^3.0.0"
52
52
  },
53
53
  "devDependencies": {
54
- "typescript": "^5.9.2",
55
- "vite": "^7.1.3",
56
- "@vitejs/plugin-vue": "^6.0.1"
54
+ "typescript": "^5.9.3",
55
+ "vite": "^7.3.1",
56
+ "@vitejs/plugin-vue": "^6.0.4"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "vite build",
@@ -0,0 +1,82 @@
1
+ <template>
2
+ <div
3
+ class="flex items-center justify-between background rounded-vsk px-6 py-3 mb-4"
4
+ :class="[{ 'border-l-4 border-green-500': isConnected }, customClass]"
5
+ @click="handleClick"
6
+ >
7
+ <div class="flex items-center gap-3 flex-1 min-w-0">
8
+ <img :src="icon" :alt="title" class="h-7 w-7 shrink-0" />
9
+ <div class="min-w-0">
10
+ <div class="font-semibold truncate">
11
+ {{ title }}
12
+ </div>
13
+ <div v-if="subtitle" class="text-xs text-gray-400 truncate">
14
+ {{ subtitle }}
15
+ </div>
16
+ <div v-if="metadata" class="text-xs text-gray-400 truncate">
17
+ {{ metadata }}
18
+ </div>
19
+ <div v-if="extraInfo && extraInfo.length > 0" class="text-xs text-gray-400 flex gap-2 mt-1">
20
+ <span v-for="(info, index) in extraInfo" :key="index">
21
+ {{ info }}
22
+ </span>
23
+ </div>
24
+ </div>
25
+ </div>
26
+
27
+ <button
28
+ v-if="showActionButton"
29
+ class="bg-vsk-primary rounded-vsk px-4 py-2 text-sm font-semibold cursor-pointer hover:opacity-70"
30
+ @click.stop="handleAction"
31
+ >
32
+ {{ actionLabel }}
33
+ </button>
34
+
35
+ <!-- Status indicator for connected state -->
36
+ <div
37
+ v-if="showStatusIndicator && isConnected"
38
+ class="w-2 h-2 rounded-full bg-green-500"
39
+ />
40
+ </div>
41
+ </template>
42
+
43
+ <script setup lang="ts">
44
+ interface Props {
45
+ icon: string;
46
+ title: string;
47
+ subtitle?: string;
48
+ metadata?: string;
49
+ extraInfo?: string[];
50
+ isConnected?: boolean;
51
+ showActionButton?: boolean;
52
+ actionLabel?: string;
53
+ showStatusIndicator?: boolean;
54
+ customClass?: string;
55
+ clickable?: boolean;
56
+ }
57
+
58
+ withDefaults(defineProps<Props>(), {
59
+ subtitle: '',
60
+ metadata: '',
61
+ extraInfo: () => [],
62
+ isConnected: false,
63
+ showActionButton: true,
64
+ actionLabel: 'Conectar',
65
+ showStatusIndicator: false,
66
+ customClass: '',
67
+ clickable: false,
68
+ });
69
+
70
+ const emit = defineEmits<{
71
+ action: [];
72
+ click: [];
73
+ }>();
74
+
75
+ const handleAction = () => {
76
+ emit('action');
77
+ };
78
+
79
+ const handleClick = () => {
80
+ emit('click');
81
+ };
82
+ </script>
@@ -0,0 +1,40 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ clickable?: boolean;
4
+ customClass?: string | Record<string, boolean>;
5
+ }
6
+
7
+ const props = withDefaults(defineProps<Props>(), {
8
+ clickable: false,
9
+ customClass: () => ({}),
10
+ });
11
+
12
+ const emit = defineEmits<{
13
+ click: [];
14
+ }>();
15
+
16
+ const handleClick = () => {
17
+ if (props.clickable) {
18
+ emit('click');
19
+ }
20
+ };
21
+ </script>
22
+
23
+ <template>
24
+ <div
25
+ :class="[
26
+ 'flex items-center justify-between background p-3 rounded-vsk border border-vsk-primary/70 transition-colors duration-200',
27
+ {
28
+ 'hover:bg-vsk-primary/5 cursor-pointer': props.clickable,
29
+ },
30
+ customClass,
31
+ ]"
32
+ @click="handleClick"
33
+ >
34
+ <slot />
35
+ </div>
36
+ </template>
37
+
38
+ <style scoped>
39
+ /* Ningún estilo adicional requerido */
40
+ </style>
@@ -0,0 +1,85 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ label: string;
4
+ disabled?: boolean;
5
+ variant?: 'primary' | 'secondary' | 'danger';
6
+ loading?: boolean;
7
+ customClass?: string | Record<string, boolean>;
8
+ size?: 'sm' | 'md' | 'lg';
9
+ fullWidth?: boolean;
10
+ iconSrc?: string;
11
+ iconAlt?: string;
12
+ iconRight?: boolean;
13
+ type?: 'button' | 'submit' | 'reset';
14
+ stopPropagation?: boolean;
15
+ preventDefault?: boolean;
16
+ }
17
+
18
+ const props = withDefaults(defineProps<Props>(), {
19
+ disabled: false,
20
+ variant: 'primary',
21
+ loading: false,
22
+ customClass: () => ({}),
23
+ size: 'md',
24
+ fullWidth: false,
25
+ iconSrc: '',
26
+ iconAlt: '',
27
+ iconRight: false,
28
+ type: 'button',
29
+ stopPropagation: false,
30
+ preventDefault: false,
31
+ });
32
+
33
+ const emit = defineEmits<{
34
+ click: [];
35
+ }>();
36
+
37
+ const variantClasses: Record<string, string> = {
38
+ primary: 'bg-primary dark:bg-primary-dark text-tx-on-primary dark:text-tx-on-primary-dark hover:bg-primary/90 dark:hover:bg-primary-dark/90',
39
+ secondary: 'bg-secondary dark:bg-secondary-dark text-tx-on-primary dark:text-tx-on-primary-dark hover:bg-secondary/80 dark:hover:bg-secondary-dark/80',
40
+ danger: 'bg-status-error dark:bg-status-error-dark text-tx-on-primary dark:text-tx-on-primary-dark hover:bg-status-error/90 dark:hover:bg-status-error-dark/90',
41
+ };
42
+
43
+ const sizeClasses: Record<'sm' | 'md' | 'lg', string> = {
44
+ sm: 'px-2 py-1 text-xs',
45
+ md: 'px-3 py-1 text-sm',
46
+ lg: 'px-4 py-2 text-base',
47
+ };
48
+
49
+ const handleClick = (event: Event) => {
50
+ if (props.stopPropagation) event.stopPropagation();
51
+ if (props.preventDefault) event.preventDefault();
52
+ if (!props.disabled && !props.loading) {
53
+ emit('click');
54
+ }
55
+ };
56
+ </script>
57
+
58
+ <template>
59
+ <button
60
+ :type="props.type"
61
+ @click="handleClick"
62
+ class="rounded-corner transition-all duration-200 disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
63
+ :class="[
64
+ variantClasses[props.variant],
65
+ sizeClasses[props.size],
66
+ props.fullWidth ? 'w-full' : '',
67
+ props.iconSrc && !props.label ? 'px-2 py-2' : '',
68
+ customClass,
69
+ ]"
70
+ :disabled="props.disabled || props.loading"
71
+ >
72
+ <span v-if="loading" class="w-4 h-4 animate-spin rounded-full border-2 border-current border-t-transparent"></span>
73
+ <template v-if="props.iconSrc && !props.iconRight">
74
+ <img :src="props.iconSrc" :alt="props.iconAlt || props.label" class="w-4 h-4" />
75
+ </template>
76
+ <span v-if="props.label">{{ props.label }}</span>
77
+ <template v-if="props.iconSrc && props.iconRight">
78
+ <img :src="props.iconSrc" :alt="props.iconAlt || props.label" class="w-4 h-4" />
79
+ </template>
80
+ </button>
81
+ </template>
82
+
83
+ <style scoped>
84
+ /* Ningún estilo adicional requerido */
85
+ </style>
@@ -0,0 +1,55 @@
1
+ <template>
2
+ <button
3
+ @click="handleClick"
4
+ class="p-2 rounded-corner background hover:opacity-50 transition-all duration-300 h-17.5 w-17.5 group relative overflow-hidden hover:scale-105 hover:shadow-lg active:scale-95"
5
+ :class="{
6
+ 'animate-pulse': isLoading,
7
+ 'ring-2 ring-primary dark:ring-primary-dark': isActive,
8
+ 'opacity-60': !isActive,
9
+ ...customClass
10
+ }"
11
+ :disabled="isLoading"
12
+ >
13
+ <img
14
+ :src="icon"
15
+ :alt="alt"
16
+ :title="tooltip"
17
+ class="m-auto w-12.5 h-12.5 transition-all duration-300 group-hover:scale-110 relative z-10"
18
+ :class="{
19
+ 'animate-spin': isLoading,
20
+ 'filter brightness-75': !isActive,
21
+ 'drop-shadow-lg': isActive,
22
+ ...iconClass
23
+ }"
24
+ />
25
+ </button>
26
+ </template>
27
+
28
+ <script setup lang="ts">
29
+ interface Props {
30
+ icon: string;
31
+ alt?: string;
32
+ tooltip?: string;
33
+ isActive?: boolean;
34
+ isLoading?: boolean;
35
+ iconClass?: Record<string, boolean>;
36
+ customClass?: Record<string, boolean>;
37
+ }
38
+
39
+ withDefaults(defineProps<Props>(), {
40
+ alt: '',
41
+ tooltip: '',
42
+ isActive: false,
43
+ isLoading: false,
44
+ iconClass: () => ({}),
45
+ customClass: () => ({}),
46
+ });
47
+
48
+ const emit = defineEmits<{
49
+ click: [];
50
+ }>();
51
+
52
+ const handleClick = () => {
53
+ emit('click');
54
+ };
55
+ </script>
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ label: string;
4
+ htmlFor?: string;
5
+ customClass?: string | Record<string, boolean>;
6
+ labelClass?: string | Record<string, boolean>;
7
+ }
8
+
9
+ withDefaults(defineProps<Props>(), {
10
+ htmlFor: '',
11
+ customClass: () => ({}),
12
+ labelClass: () => ({}),
13
+ });
14
+ </script>
15
+
16
+ <template>
17
+ <div class="flex flex-col gap-2" :class="customClass">
18
+ <label v-if="label" :for="htmlFor" class="text-sm font-medium text-primary dark:text-primary-dark" :class="labelClass">
19
+ {{ label }}
20
+ </label>
21
+ <slot />
22
+ </div>
23
+ </template>
24
+
25
+ <style scoped>
26
+ /* Ningún estilo adicional requerido */
27
+ </style>
@@ -0,0 +1,100 @@
1
+ <template>
2
+ <div
3
+ class="background rounded-corner flex flex-row items-center gap-2 justify-between w-full h-auto p-4 transition-all duration-200 hover:bg-ui-surface/80 dark:hover:bg-ui-surface-dark/80"
4
+ >
5
+ <button
6
+ v-if="showButton"
7
+ @click="handleButtonClick"
8
+ class="w-8 h-8 flex items-center justify-center rounded-corner transition-all duration-200 hover:bg-ui-surface/80 dark:hover:bg-ui-surface-dark/80 hover:scale-110 active:scale-95"
9
+ >
10
+ <img
11
+ :src="icon"
12
+ :alt="alt"
13
+ :title="tooltip"
14
+ class="w-6 h-6 transition-all duration-200"
15
+ :class="iconClass"
16
+ />
17
+ </button>
18
+
19
+ <div
20
+ v-else
21
+ class="w-8 h-8 flex items-center justify-center"
22
+ >
23
+ <img
24
+ :src="icon"
25
+ :alt="alt"
26
+ class="w-6 h-6 transition-all duration-200"
27
+ />
28
+ </div>
29
+
30
+ <input
31
+ type="range"
32
+ :min="min"
33
+ :max="max"
34
+ :value="modelValue"
35
+ @input="handleInput"
36
+ class="flex-1 transition-all duration-200 hover:scale-105"
37
+ />
38
+
39
+ <span
40
+ class="w-12 text-right transition-all duration-200 font-medium"
41
+ :class="percentageClass"
42
+ >
43
+ {{ percentage }}%
44
+ </span>
45
+ </div>
46
+ </template>
47
+
48
+ <script setup lang="ts">
49
+ import { computed } from 'vue';
50
+
51
+ interface Props {
52
+ icon: string;
53
+ alt?: string;
54
+ tooltip?: string;
55
+ modelValue: number;
56
+ min?: number;
57
+ max?: number;
58
+ showButton?: boolean;
59
+ iconClass?: string | Record<string, boolean>;
60
+ getPercentageClass?: (percentage: number) => string;
61
+ }
62
+
63
+ const props = withDefaults(defineProps<Props>(), {
64
+ alt: '',
65
+ tooltip: '',
66
+ min: 0,
67
+ max: 100,
68
+ showButton: false,
69
+ iconClass: () => ({}),
70
+ getPercentageClass: () => '',
71
+ });
72
+
73
+ const emit = defineEmits<{
74
+ 'update:modelValue': [value: number];
75
+ 'buttonClick': [];
76
+ }>();
77
+
78
+ const percentage = computed(() => {
79
+ if (props.max <= props.min) return 0;
80
+ const range = props.max - props.min;
81
+ const value = props.modelValue - props.min;
82
+ return Math.round((value / range) * 100);
83
+ });
84
+
85
+ const percentageClass = computed(() => {
86
+ if (props.getPercentageClass) {
87
+ return props.getPercentageClass(percentage.value);
88
+ }
89
+ return '';
90
+ });
91
+
92
+ const handleInput = (event: Event) => {
93
+ const target = event.target as HTMLInputElement;
94
+ emit('update:modelValue', Number(target.value));
95
+ };
96
+
97
+ const handleButtonClick = () => {
98
+ emit('buttonClick');
99
+ };
100
+ </script>
@@ -0,0 +1,49 @@
1
+ <template>
2
+ <button
3
+ type="button"
4
+ @click="handleClick"
5
+ :disabled="disabled"
6
+ :class="[
7
+ 'relative inline-flex items-center rounded-full transition-colors',
8
+ size === 'small' ? 'h-6 w-11' : 'h-7 w-12',
9
+ isOn ? activeClass : inactiveClass,
10
+ disabled ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer',
11
+ customClass
12
+ ]"
13
+ >
14
+ <span
15
+ :class="[
16
+ 'inline-block transform rounded-full bg-white shadow transition-transform',
17
+ size === 'small' ? 'h-4 w-4' : 'h-6 w-6',
18
+ isOn ? (size === 'small' ? 'translate-x-6' : 'translate-x-5') : 'translate-x-1'
19
+ ]"
20
+ ></span>
21
+ </button>
22
+ </template>
23
+
24
+ <script setup lang="ts">
25
+ interface Props {
26
+ isOn: boolean;
27
+ disabled?: boolean;
28
+ size?: 'small' | 'medium';
29
+ activeClass?: string;
30
+ inactiveClass?: string;
31
+ customClass?: string;
32
+ }
33
+
34
+ const props = withDefaults(defineProps<Props>(), {
35
+ disabled: false,
36
+ size: 'small',
37
+ activeClass: 'bg-primary dark:bg-primary-dark',
38
+ inactiveClass: 'background',
39
+ customClass: '',
40
+ });
41
+
42
+ const emit = defineEmits<{
43
+ toggle: [value: boolean];
44
+ }>();
45
+
46
+ const handleClick = () => {
47
+ emit('toggle', !props.isOn);
48
+ };
49
+ </script>
package/src/index.ts CHANGED
@@ -1,9 +1,31 @@
1
1
  import SideBar from "./sidebar/SideBar.vue";
2
2
  import SideButton from "./sidebar/SideButton.vue";
3
3
  import WindowFrame from "./window/WindowFrame.vue";
4
+ import ActionButton from "./controls/ActionButton.vue";
5
+ import ConfigSection from "./layout/ConfigSection.vue";
6
+ import DeviceCard from "./cards/DeviceCard.vue";
7
+ import FormGroup from "./forms/FormGroup.vue";
8
+ import ListCard from "./cards/ListCard.vue";
9
+ import SliderControl from "./forms/SliderControl.vue";
10
+ import SwitchToggle from "./forms/SwitchToggle.vue";
11
+ import ToggleControl from "./controls/ToggleControl.vue";
12
+ import TrayIconButton from "./tray/TrayIconButton.vue";
4
13
  import type { App } from "vue";
5
14
 
6
- const components = [SideBar, SideButton, WindowFrame];
15
+ const components = [
16
+ SideBar,
17
+ SideButton,
18
+ WindowFrame,
19
+ ActionButton,
20
+ ConfigSection,
21
+ DeviceCard,
22
+ FormGroup,
23
+ ListCard,
24
+ SliderControl,
25
+ SwitchToggle,
26
+ ToggleControl,
27
+ TrayIconButton,
28
+ ];
7
29
 
8
30
  export default {
9
31
  install(app: App) {
@@ -13,4 +35,17 @@ export default {
13
35
  },
14
36
  };
15
37
 
16
- export { SideBar, SideButton, WindowFrame };
38
+ export {
39
+ SideBar,
40
+ SideButton,
41
+ WindowFrame,
42
+ ActionButton,
43
+ ConfigSection,
44
+ DeviceCard,
45
+ FormGroup,
46
+ ListCard,
47
+ SliderControl,
48
+ SwitchToggle,
49
+ ToggleControl,
50
+ TrayIconButton,
51
+ };
@@ -0,0 +1,28 @@
1
+ <script setup lang="ts">
2
+ interface Props {
3
+ title: string;
4
+ icon?: string;
5
+ customClass?: string | Record<string, boolean>;
6
+ }
7
+
8
+ withDefaults(defineProps<Props>(), {
9
+ icon: '',
10
+ customClass: () => ({}),
11
+ });
12
+ </script>
13
+
14
+ <template>
15
+ <div
16
+ class="flex flex-col gap-4 p-4 background rounded-corner"
17
+ :class="customClass"
18
+ >
19
+ <h3 class="text-base font-semibold m-0 text-primary dark:text-primary-dark">
20
+ {{ icon ? `${icon} ${title}` : title }}
21
+ </h3>
22
+ <slot />
23
+ </div>
24
+ </template>
25
+
26
+ <style scoped>
27
+ /* Ningún estilo adicional requerido */
28
+ </style>
@@ -1,6 +1,4 @@
1
1
  <script lang="ts" setup>
2
- import { defineComponent } from "vue";
3
-
4
2
  const props = defineProps({
5
3
  title: {
6
4
  type: String,
@@ -15,6 +13,6 @@ const props = defineProps({
15
13
 
16
14
  <template>
17
15
  <a href="#" class="sidebar-button">
18
- <img :src="image" class="img-fluid" />
16
+ <img :src="image" :alt="title" class="img-fluid" />
19
17
  </a>
20
18
  </template>
@@ -0,0 +1,80 @@
1
+ <template>
2
+ <div
3
+ class="p-1 rounded-corner relative hover:bg-primary dark:hover:bg-primary-dark group transition-all duration-300"
4
+ :class="customClass"
5
+ :title="tooltip"
6
+ @click="handleClick"
7
+ @mouseenter="showTooltip = true"
8
+ @mouseleave="showTooltip = false"
9
+ >
10
+ <img
11
+ :src="icon"
12
+ :alt="alt"
13
+ class="m-auto h-5.5 w-auto transition-all duration-300"
14
+ :class="iconClass"
15
+ />
16
+
17
+ <!-- Badge/Counter -->
18
+ <div
19
+ v-if="badge !== null && badge > 0"
20
+ class="absolute bottom-1 right-1 bg-primary dark:bg-primary-dark text-white text-xs rounded-full w-4 h-4 flex items-center justify-center font-bold animate-bounce"
21
+ >
22
+ {{ badge }}
23
+ </div>
24
+
25
+ <!-- Tooltip personalizado -->
26
+ <div
27
+ v-if="showCustomTooltip && customTooltipText"
28
+ class="absolute top-1 left-1/2 transform -translate-x-1/2 text-xs font-semibold p-1 rounded-corner transition-all duration-300 pointer-events-none background"
29
+ :class="[
30
+ tooltipClass,
31
+ {
32
+ 'opacity-0 -translate-y-2': !showTooltip,
33
+ 'opacity-100 translate-y-0': showTooltip
34
+ }
35
+ ]"
36
+ >
37
+ {{ customTooltipText }}
38
+ </div>
39
+
40
+ <!-- Slot para contenido adicional personalizado -->
41
+ <slot></slot>
42
+ </div>
43
+ </template>
44
+
45
+ <script setup lang="ts">
46
+ import { ref } from 'vue';
47
+
48
+ interface Props {
49
+ icon: string;
50
+ alt?: string;
51
+ tooltip?: string;
52
+ badge?: number | null;
53
+ iconClass?: string | Record<string, boolean>;
54
+ customClass?: string | Record<string, boolean>;
55
+ tooltipClass?: string | Record<string, boolean>;
56
+ showCustomTooltip?: boolean;
57
+ customTooltipText?: string;
58
+ }
59
+
60
+ withDefaults(defineProps<Props>(), {
61
+ alt: '',
62
+ tooltip: '',
63
+ badge: null,
64
+ iconClass: () => ({}),
65
+ customClass: '',
66
+ tooltipClass: '',
67
+ showCustomTooltip: false,
68
+ customTooltipText: '',
69
+ });
70
+
71
+ const emit = defineEmits<{
72
+ click: [];
73
+ }>();
74
+
75
+ const showTooltip = ref(false);
76
+
77
+ const handleClick = () => {
78
+ emit('click');
79
+ };
80
+ </script>