@vasakgroup/vue-libvasak 0.3.0 → 0.3.2
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/dist/types/cards/DeviceCard.vue.d.ts +32 -0
- package/dist/types/cards/ListCard.vue.d.ts +24 -0
- package/dist/types/controls/ActionButton.vue.d.ts +35 -0
- package/dist/types/controls/ToggleControl.vue.d.ts +23 -0
- package/dist/types/forms/FormGroup.vue.d.ts +23 -0
- package/dist/types/forms/SelectField.vue.d.ts +24 -0
- package/dist/types/forms/SliderControl.vue.d.ts +28 -0
- package/dist/types/forms/SwitchToggle.vue.d.ts +21 -0
- package/dist/types/icons/ThemeIcon.vue.d.ts +13 -0
- package/{src/index.ts → dist/types/index.d.ts} +6 -40
- package/dist/types/internos/iconoDelTema.d.ts +14 -0
- package/dist/types/layout/ConfigSection.vue.d.ts +21 -0
- package/dist/types/sidebar/SideBar.vue.d.ts +51 -0
- package/dist/types/sidebar/SideButton.vue.d.ts +21 -0
- package/dist/types/sidebar/SideGroup.vue.d.ts +21 -0
- package/dist/types/sidebar/tipos.d.ts +15 -0
- package/dist/types/tray/TrayIconButton.vue.d.ts +37 -0
- package/dist/types/window/TopBar.vue.d.ts +9 -0
- package/dist/types/window/WindowFrame.vue.d.ts +20 -0
- package/dist/vue-libvasak.es.js +358 -275
- package/dist/vue-libvasak.umd.js +1 -1
- package/package.json +7 -7
- package/src/cards/DeviceCard.vue +0 -82
- package/src/cards/ListCard.vue +0 -40
- package/src/controls/ActionButton.vue +0 -85
- package/src/controls/ToggleControl.vue +0 -55
- package/src/forms/FormGroup.vue +0 -27
- package/src/forms/SliderControl.vue +0 -100
- package/src/forms/SwitchToggle.vue +0 -49
- package/src/layout/ConfigSection.vue +0 -28
- package/src/shims-vue.d.ts +0 -15
- package/src/sidebar/SideBar.vue +0 -183
- package/src/sidebar/SideButton.vue +0 -115
- package/src/sidebar/SideGroup.vue +0 -46
- package/src/sidebar/tipos.ts +0 -16
- package/src/tray/TrayIconButton.vue +0 -80
- package/src/types/vue-libvasak.d.ts +0 -191
- package/src/window/TopBar.vue +0 -46
- package/src/window/WindowFrame.vue +0 -21
package/src/sidebar/SideBar.vue
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
/**
|
|
3
|
-
* La barra lateral de las aplicaciones de VasakOS.
|
|
4
|
-
*
|
|
5
|
-
* Es la de Configuración, que es la que todas las demás venían copiando: mismo
|
|
6
|
-
* `aside` con borde y esquina redondeada, mismo plegado a 84 píxeles, mismos
|
|
7
|
-
* grupos con título. Que estén acá y no copiadas en cada repositorio es lo que
|
|
8
|
-
* hace que las ventanas se lean como partes del mismo escritorio en vez de
|
|
9
|
-
* parecerse por casualidad.
|
|
10
|
-
*
|
|
11
|
-
* # Los dos caminos
|
|
12
|
-
*
|
|
13
|
-
* Con `categories` se arma sola: cada categoría es un grupo plegable y cada
|
|
14
|
-
* elemento un botón, con el activo marcado contra `modelValue`. Es el camino de
|
|
15
|
-
* Configuración y del monitor, donde la barra **es** la navegación.
|
|
16
|
-
*
|
|
17
|
-
* Con la ranura por omisión se pone cualquier cosa adentro —discos, pasos de un
|
|
18
|
-
* asistente, una línea de tiempo— y la barra aporta nada más que el marco y el
|
|
19
|
-
* plegado. La ranura recibe `collapsed`, porque lo que se dibuja adentro casi
|
|
20
|
-
* siempre tiene que saberlo.
|
|
21
|
-
*
|
|
22
|
-
* Los dos caminos conviven: lo declarativo va primero y la ranura después.
|
|
23
|
-
*
|
|
24
|
-
* # Por qué no hay traducciones acá
|
|
25
|
-
*
|
|
26
|
-
* Una librería de componentes que traduce obliga a todas las aplicaciones a
|
|
27
|
-
* compartir sus claves. Los textos entran por propiedades; el `aria-label` del
|
|
28
|
-
* botón de plegar también, que es el único que no se ve pero se oye.
|
|
29
|
-
*/
|
|
30
|
-
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
31
|
-
import SideGroup from './SideGroup.vue';
|
|
32
|
-
import SideButton from './SideButton.vue';
|
|
33
|
-
import type { SidebarCategory } from './tipos';
|
|
34
|
-
|
|
35
|
-
const props = withDefaults(
|
|
36
|
-
defineProps<{
|
|
37
|
-
/** El nombre de la ventana. Sin él y sin `subtitle` no hay área de título. */
|
|
38
|
-
title?: string;
|
|
39
|
-
subtitle?: string;
|
|
40
|
-
categories?: SidebarCategory[];
|
|
41
|
-
/** El identificador del elemento activo. */
|
|
42
|
-
modelValue?: string;
|
|
43
|
-
/** Plegada desde afuera. Sin esto, la barra se maneja sola. */
|
|
44
|
-
collapsed?: boolean;
|
|
45
|
-
/** Lo que oye un lector de pantalla en el botón de plegar. */
|
|
46
|
-
collapseLabel?: string;
|
|
47
|
-
expandLabel?: string;
|
|
48
|
-
}>(),
|
|
49
|
-
{
|
|
50
|
-
title: '',
|
|
51
|
-
subtitle: '',
|
|
52
|
-
categories: () => [],
|
|
53
|
-
modelValue: '',
|
|
54
|
-
collapsed: undefined,
|
|
55
|
-
collapseLabel: 'Collapse',
|
|
56
|
-
expandLabel: 'Expand',
|
|
57
|
-
}
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
const emit = defineEmits<{
|
|
61
|
-
'update:modelValue': [value: string];
|
|
62
|
-
'update:collapsed': [value: boolean];
|
|
63
|
-
change: [value: string];
|
|
64
|
-
}>();
|
|
65
|
-
|
|
66
|
-
const plegadaAMano = ref(props.collapsed ?? false);
|
|
67
|
-
const esAngosta = ref(false);
|
|
68
|
-
let consulta: MediaQueryList | null = null;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Plegada por decisión o por ancho.
|
|
72
|
-
*
|
|
73
|
-
* Por debajo de 768 píxeles no hay lugar para el texto de los botones, así que
|
|
74
|
-
* la barra se pliega sola y el botón de plegar no se muestra: ofrecer
|
|
75
|
-
* desplegarla ahí sería ofrecer algo que no entra.
|
|
76
|
-
*/
|
|
77
|
-
const plegada = computed(() => esAngosta.value || plegadaAMano.value);
|
|
78
|
-
const hayTitulo = computed(() => Boolean(props.title || props.subtitle));
|
|
79
|
-
const hayCategorias = computed(() => props.categories.length > 0);
|
|
80
|
-
|
|
81
|
-
function revisar() {
|
|
82
|
-
esAngosta.value = consulta?.matches ?? false;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
function alternar() {
|
|
86
|
-
plegadaAMano.value = !plegadaAMano.value;
|
|
87
|
-
emit('update:collapsed', plegadaAMano.value);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function elegir(id: string) {
|
|
91
|
-
emit('update:modelValue', id);
|
|
92
|
-
emit('change', id);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Controlada desde afuera cuando la aplicación pasa `collapsed`: así dos
|
|
96
|
-
// ventanas de la misma aplicación pueden recordar cómo la dejó la persona.
|
|
97
|
-
watch(
|
|
98
|
-
() => props.collapsed,
|
|
99
|
-
(valor) => {
|
|
100
|
-
if (valor !== undefined) {
|
|
101
|
-
plegadaAMano.value = valor;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
);
|
|
105
|
-
|
|
106
|
-
onMounted(() => {
|
|
107
|
-
consulta = window.matchMedia('(max-width: 767px)');
|
|
108
|
-
revisar();
|
|
109
|
-
consulta.addEventListener('change', revisar);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
onBeforeUnmount(() => consulta?.removeEventListener('change', revisar));
|
|
113
|
-
|
|
114
|
-
defineExpose({ collapsed: plegada });
|
|
115
|
-
</script>
|
|
116
|
-
|
|
117
|
-
<template>
|
|
118
|
-
<aside
|
|
119
|
-
class="relative z-30 flex h-full shrink-0 flex-col rounded-corner border border-ui-border bg-ui-bg/80 transition-all duration-300"
|
|
120
|
-
:class="['w-[84px]', plegada ? 'md:w-[84px]' : 'md:w-72']">
|
|
121
|
-
<header
|
|
122
|
-
v-if="hayTitulo || $slots.header"
|
|
123
|
-
class="flex flex-col gap-2 border-ui-border border-b p-2">
|
|
124
|
-
<div class="flex items-center gap-2">
|
|
125
|
-
<button
|
|
126
|
-
type="button"
|
|
127
|
-
class="hidden h-10 w-10 items-center justify-center rounded-corner border border-ui-border bg-ui-surface/70 font-semibold text-sm md:inline-flex"
|
|
128
|
-
:aria-label="plegada ? expandLabel : collapseLabel"
|
|
129
|
-
:aria-expanded="!plegada"
|
|
130
|
-
@click="alternar">
|
|
131
|
-
{{ plegada ? '>' : '<' }}
|
|
132
|
-
</button>
|
|
133
|
-
<!-- El área de título es opcional: hay ventanas donde el nombre ya está
|
|
134
|
-
en la barra superior y repetirlo acá gasta la mitad del alto. -->
|
|
135
|
-
<div v-if="hayTitulo && !plegada" class="min-w-0 flex-1">
|
|
136
|
-
<p v-if="title" class="truncate font-semibold text-sm">{{ title }}</p>
|
|
137
|
-
<p v-if="subtitle" class="truncate text-tx-muted text-xs">{{ subtitle }}</p>
|
|
138
|
-
</div>
|
|
139
|
-
</div>
|
|
140
|
-
|
|
141
|
-
<!-- Lo que va antes que cualquier categoría: la búsqueda de la tienda,
|
|
142
|
-
por ejemplo. Plegada no entra un campo de texto —84 píxeles es el
|
|
143
|
-
ancho del icono— así que se esconde en vez de quedar ilegible. -->
|
|
144
|
-
<div v-if="$slots.header && !plegada">
|
|
145
|
-
<slot name="header" :collapsed="plegada" />
|
|
146
|
-
</div>
|
|
147
|
-
</header>
|
|
148
|
-
|
|
149
|
-
<!-- Sin área de título el botón de plegar necesita su propio lugar, o la
|
|
150
|
-
barra deja de poder plegarse. -->
|
|
151
|
-
<div v-else class="flex justify-center border-ui-border border-b p-2">
|
|
152
|
-
<button
|
|
153
|
-
type="button"
|
|
154
|
-
class="hidden h-10 w-10 items-center justify-center rounded-corner border border-ui-border bg-ui-surface/70 font-semibold text-sm md:inline-flex"
|
|
155
|
-
:aria-label="plegada ? expandLabel : collapseLabel"
|
|
156
|
-
:aria-expanded="!plegada"
|
|
157
|
-
@click="alternar">
|
|
158
|
-
{{ plegada ? '>' : '<' }}
|
|
159
|
-
</button>
|
|
160
|
-
</div>
|
|
161
|
-
|
|
162
|
-
<div class="flex-1 space-y-3 overflow-y-auto p-2">
|
|
163
|
-
<SideGroup
|
|
164
|
-
v-for="category in categories"
|
|
165
|
-
:key="category.id"
|
|
166
|
-
:title="category.title"
|
|
167
|
-
:collapsed="plegada">
|
|
168
|
-
<SideButton
|
|
169
|
-
v-for="item in category.items"
|
|
170
|
-
:key="item.id"
|
|
171
|
-
:label="item.label"
|
|
172
|
-
:icon="item.icon"
|
|
173
|
-
:badge="item.badge"
|
|
174
|
-
:disabled="item.disabled"
|
|
175
|
-
:collapsed="plegada"
|
|
176
|
-
:active="modelValue === item.id"
|
|
177
|
-
@click="elegir(item.id)" />
|
|
178
|
-
</SideGroup>
|
|
179
|
-
|
|
180
|
-
<slot :collapsed="plegada" />
|
|
181
|
-
</div>
|
|
182
|
-
</aside>
|
|
183
|
-
</template>
|
|
@@ -1,115 +0,0 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
/**
|
|
3
|
-
* Un elemento de la barra lateral.
|
|
4
|
-
*
|
|
5
|
-
* El icono sale del tema del escritorio y se vuelve a resolver cuando la
|
|
6
|
-
* persona cambia de tema: por eso no se recibe una ruta sino un nombre. Plegado
|
|
7
|
-
* queda sólo el icono, y el nombre pasa al `title` para que el globo lo diga —
|
|
8
|
-
* sin eso, una barra plegada es una columna de dibujos sin explicación.
|
|
9
|
-
*/
|
|
10
|
-
import { getIconSource } from '@vasakgroup/plugin-vicons';
|
|
11
|
-
import { listen, type UnlistenFn } from '@tauri-apps/api/event';
|
|
12
|
-
import { onMounted, onUnmounted, ref, toRef, watch } from 'vue';
|
|
13
|
-
|
|
14
|
-
const props = withDefaults(
|
|
15
|
-
defineProps<{
|
|
16
|
-
label: string;
|
|
17
|
-
icon?: string;
|
|
18
|
-
active?: boolean;
|
|
19
|
-
collapsed?: boolean;
|
|
20
|
-
disabled?: boolean;
|
|
21
|
-
badge?: string | number;
|
|
22
|
-
}>(),
|
|
23
|
-
{ icon: '', active: false, collapsed: false, disabled: false, badge: '' }
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
defineEmits<{ click: [] }>();
|
|
27
|
-
|
|
28
|
-
const fuente = ref('');
|
|
29
|
-
const icono = toRef(props, 'icon');
|
|
30
|
-
let soltar: UnlistenFn | null = null;
|
|
31
|
-
let desmontado = false;
|
|
32
|
-
|
|
33
|
-
/** Cuántas resoluciones se pidieron. De las que estén en vuelo, sólo vale la última. */
|
|
34
|
-
let ultimoPedido = 0;
|
|
35
|
-
|
|
36
|
-
async function resolver() {
|
|
37
|
-
const mio = ++ultimoPedido;
|
|
38
|
-
const nombre = icono.value;
|
|
39
|
-
if (!nombre) {
|
|
40
|
-
fuente.value = '';
|
|
41
|
-
return;
|
|
42
|
-
}
|
|
43
|
-
const resuelto = await getIconSource(nombre);
|
|
44
|
-
// Cambiar de icono y cambiar de tema resuelven en paralelo, y el tema tarda
|
|
45
|
-
// lo que tarde el backend. Sin el testigo, la respuesta vieja llega última y
|
|
46
|
-
// deja puesto el icono anterior — que es el mismo síntoma que se venía a
|
|
47
|
-
// evitar, pero intermitente y según cuál tarde más.
|
|
48
|
-
if (mio === ultimoPedido) {
|
|
49
|
-
fuente.value = resuelto;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
onMounted(async () => {
|
|
54
|
-
// El oyente **antes** de la primera resolución. El tema de iconos cambia en
|
|
55
|
-
// caliente, y resolver el primero tarda: un cambio de tema durante esa
|
|
56
|
-
// espera no lo escuchaba nadie, y el botón se quedaba con el icono del tema
|
|
57
|
-
// anterior hasta el cambio siguiente.
|
|
58
|
-
const dejarDeEscuchar = await listen('vicons:theme-changed', resolver);
|
|
59
|
-
// Registrarse tarda, y en una lista que se desplaza un botón puede irse
|
|
60
|
-
// antes de que termine. Ahí `onUnmounted` ya pasó y no vio nada que soltar:
|
|
61
|
-
// el oyente quedaba registrado para siempre sobre un componente muerto.
|
|
62
|
-
if (desmontado) {
|
|
63
|
-
dejarDeEscuchar();
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
soltar = dejarDeEscuchar;
|
|
67
|
-
|
|
68
|
-
// Las dos resoluciones pueden cruzarse —ésta y la que dispare un cambio de
|
|
69
|
-
// tema—, y de eso se encarga el testigo de `resolver`.
|
|
70
|
-
await resolver();
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
onUnmounted(() => {
|
|
74
|
-
desmontado = true;
|
|
75
|
-
soltar?.();
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
watch(icono, resolver);
|
|
79
|
-
</script>
|
|
80
|
-
|
|
81
|
-
<template>
|
|
82
|
-
<button
|
|
83
|
-
type="button"
|
|
84
|
-
:title="collapsed ? label : undefined"
|
|
85
|
-
:aria-label="collapsed ? label : undefined"
|
|
86
|
-
:disabled="disabled"
|
|
87
|
-
:aria-current="active ? 'page' : undefined"
|
|
88
|
-
class="group relative flex w-full items-center gap-3 rounded-corner border px-3 py-2 text-left text-sm transition-all duration-200"
|
|
89
|
-
:class="[
|
|
90
|
-
active
|
|
91
|
-
? 'border-secondary bg-primary/15 text-tx-main shadow-sm'
|
|
92
|
-
: 'border-transparent bg-ui-bg/30 hover:border-ui-border hover:bg-ui-surface/70',
|
|
93
|
-
disabled ? 'cursor-not-allowed opacity-60' : 'cursor-pointer',
|
|
94
|
-
collapsed ? 'justify-center px-2' : '',
|
|
95
|
-
]"
|
|
96
|
-
@click="$emit('click')">
|
|
97
|
-
<span
|
|
98
|
-
class="flex h-8 w-8 shrink-0 items-center justify-center rounded-corner font-semibold text-xs uppercase tracking-wide"
|
|
99
|
-
:class="active ? 'border-secondary bg-primary/20' : ''"
|
|
100
|
-
aria-hidden="true">
|
|
101
|
-
<img v-if="fuente" :src="fuente" alt="" class="h-8 w-8 object-contain">
|
|
102
|
-
<!-- Sin icono, la inicial: un hueco vacío del mismo tamaño deja la fila
|
|
103
|
-
desalineada contra las que sí lo tienen. -->
|
|
104
|
-
<span v-else>{{ label.charAt(0).toUpperCase() }}</span>
|
|
105
|
-
</span>
|
|
106
|
-
|
|
107
|
-
<span v-if="!collapsed" class="min-w-0 flex-1 truncate font-medium">{{ label }}</span>
|
|
108
|
-
|
|
109
|
-
<span
|
|
110
|
-
v-if="!collapsed && badge !== ''"
|
|
111
|
-
class="rounded-corner bg-ui-surface px-2 py-0.5 font-semibold text-tx-muted text-xs">
|
|
112
|
-
{{ badge }}
|
|
113
|
-
</span>
|
|
114
|
-
</button>
|
|
115
|
-
</template>
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
/**
|
|
3
|
-
* Un grupo de elementos de la barra, con su título plegable.
|
|
4
|
-
*
|
|
5
|
-
* Plegada la barra, el título del grupo no se muestra —no entra— y el grupo se
|
|
6
|
-
* abre sí o sí: un grupo cerrado sin título visible sería contenido escondido
|
|
7
|
-
* detrás de nada.
|
|
8
|
-
*/
|
|
9
|
-
import { ref, watch } from 'vue';
|
|
10
|
-
|
|
11
|
-
const props = withDefaults(
|
|
12
|
-
defineProps<{ title: string; collapsed?: boolean; defaultOpen?: boolean }>(),
|
|
13
|
-
{ collapsed: false, defaultOpen: true }
|
|
14
|
-
);
|
|
15
|
-
|
|
16
|
-
const abierto = ref(props.defaultOpen);
|
|
17
|
-
|
|
18
|
-
watch(
|
|
19
|
-
() => props.collapsed,
|
|
20
|
-
(plegada) => {
|
|
21
|
-
if (plegada) {
|
|
22
|
-
abierto.value = true;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
);
|
|
26
|
-
</script>
|
|
27
|
-
|
|
28
|
-
<template>
|
|
29
|
-
<section class="flex flex-col gap-2">
|
|
30
|
-
<button
|
|
31
|
-
v-if="!collapsed"
|
|
32
|
-
type="button"
|
|
33
|
-
class="group flex w-full items-center justify-between rounded-corner px-2 py-1 text-tx-muted text-xs uppercase tracking-[0.08em] hover:bg-ui-surface/60"
|
|
34
|
-
:aria-expanded="abierto"
|
|
35
|
-
@click="abierto = !abierto">
|
|
36
|
-
<span>{{ title }}</span>
|
|
37
|
-
<span class="text-[10px] transition-transform duration-200" :class="abierto ? 'rotate-180' : ''">
|
|
38
|
-
v
|
|
39
|
-
</span>
|
|
40
|
-
</button>
|
|
41
|
-
|
|
42
|
-
<div v-if="abierto || collapsed" class="flex flex-col gap-1">
|
|
43
|
-
<slot />
|
|
44
|
-
</div>
|
|
45
|
-
</section>
|
|
46
|
-
</template>
|
package/src/sidebar/tipos.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/** Un elemento de la barra lateral: un lugar al que se va. */
|
|
2
|
-
export interface SidebarItem {
|
|
3
|
-
id: string;
|
|
4
|
-
label: string;
|
|
5
|
-
/** Un nombre del tema de iconos del escritorio, no una ruta. */
|
|
6
|
-
icon?: string;
|
|
7
|
-
badge?: string | number;
|
|
8
|
-
disabled?: boolean;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
/** Un grupo de elementos, con su título plegable. */
|
|
12
|
-
export interface SidebarCategory {
|
|
13
|
-
id: string;
|
|
14
|
-
title: string;
|
|
15
|
-
items: SidebarItem[];
|
|
16
|
-
}
|
|
@@ -1,80 +0,0 @@
|
|
|
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>
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
declare module "@vasakgroup/vue-libvasak" {
|
|
2
|
-
import { DefineComponent, App } from "vue";
|
|
3
|
-
|
|
4
|
-
/* Layout */
|
|
5
|
-
export interface WindowFrameProps {
|
|
6
|
-
title?: string;
|
|
7
|
-
image?: string;
|
|
8
|
-
class?: string;
|
|
9
|
-
[key: string]: any;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/* Sidebar */
|
|
13
|
-
export interface SidebarItem {
|
|
14
|
-
id: string;
|
|
15
|
-
label: string;
|
|
16
|
-
/** Un nombre del tema de iconos del escritorio, no una ruta. */
|
|
17
|
-
icon?: string;
|
|
18
|
-
badge?: string | number;
|
|
19
|
-
disabled?: boolean;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export interface SidebarCategory {
|
|
23
|
-
id: string;
|
|
24
|
-
title: string;
|
|
25
|
-
items: SidebarItem[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export interface SideBarProps {
|
|
29
|
-
/** Sin `title` ni `subtitle` no se dibuja el área de título. */
|
|
30
|
-
title?: string;
|
|
31
|
-
subtitle?: string;
|
|
32
|
-
categories?: SidebarCategory[];
|
|
33
|
-
modelValue?: string;
|
|
34
|
-
collapsed?: boolean;
|
|
35
|
-
collapseLabel?: string;
|
|
36
|
-
expandLabel?: string;
|
|
37
|
-
class?: string;
|
|
38
|
-
[key: string]: any;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface SideButtonProps {
|
|
42
|
-
label: string;
|
|
43
|
-
/** Un nombre del tema de iconos, que se vuelve a resolver al cambiar de tema. */
|
|
44
|
-
icon?: string;
|
|
45
|
-
active?: boolean;
|
|
46
|
-
collapsed?: boolean;
|
|
47
|
-
disabled?: boolean;
|
|
48
|
-
badge?: string | number;
|
|
49
|
-
class?: string;
|
|
50
|
-
[key: string]: any;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export interface SideGroupProps {
|
|
54
|
-
title: string;
|
|
55
|
-
collapsed?: boolean;
|
|
56
|
-
defaultOpen?: boolean;
|
|
57
|
-
[key: string]: any;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/* Controls */
|
|
61
|
-
export interface ActionButtonProps {
|
|
62
|
-
label: string;
|
|
63
|
-
disabled?: boolean;
|
|
64
|
-
variant?: "primary" | "secondary" | "danger";
|
|
65
|
-
loading?: boolean;
|
|
66
|
-
customClass?: string | Record<string, boolean>;
|
|
67
|
-
size?: "sm" | "md" | "lg";
|
|
68
|
-
fullWidth?: boolean;
|
|
69
|
-
iconSrc?: string;
|
|
70
|
-
iconAlt?: string;
|
|
71
|
-
iconRight?: boolean;
|
|
72
|
-
type?: "button" | "submit" | "reset";
|
|
73
|
-
stopPropagation?: boolean;
|
|
74
|
-
preventDefault?: boolean;
|
|
75
|
-
[key: string]: any;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export interface ToggleControlProps {
|
|
79
|
-
icon: string;
|
|
80
|
-
alt?: string;
|
|
81
|
-
tooltip?: string;
|
|
82
|
-
isActive?: boolean;
|
|
83
|
-
isLoading?: boolean;
|
|
84
|
-
iconClass?: Record<string, boolean>;
|
|
85
|
-
customClass?: Record<string, boolean>;
|
|
86
|
-
[key: string]: any;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/* Layout/Config */
|
|
90
|
-
export interface ConfigSectionProps {
|
|
91
|
-
title: string;
|
|
92
|
-
icon?: string;
|
|
93
|
-
customClass?: string | Record<string, boolean>;
|
|
94
|
-
[key: string]: any;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/* Cards */
|
|
98
|
-
export interface DeviceCardProps {
|
|
99
|
-
icon: string;
|
|
100
|
-
title: string;
|
|
101
|
-
subtitle?: string;
|
|
102
|
-
metadata?: string;
|
|
103
|
-
extraInfo?: string[];
|
|
104
|
-
isConnected?: boolean;
|
|
105
|
-
showActionButton?: boolean;
|
|
106
|
-
actionLabel?: string;
|
|
107
|
-
showStatusIndicator?: boolean;
|
|
108
|
-
customClass?: string;
|
|
109
|
-
clickable?: boolean;
|
|
110
|
-
[key: string]: any;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export interface ListCardProps {
|
|
114
|
-
clickable?: boolean;
|
|
115
|
-
customClass?: string | Record<string, boolean>;
|
|
116
|
-
[key: string]: any;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/* Forms */
|
|
120
|
-
export interface FormGroupProps {
|
|
121
|
-
label: string;
|
|
122
|
-
htmlFor?: string;
|
|
123
|
-
customClass?: string | Record<string, boolean>;
|
|
124
|
-
labelClass?: string | Record<string, boolean>;
|
|
125
|
-
[key: string]: any;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
export interface SliderControlProps {
|
|
129
|
-
icon: string;
|
|
130
|
-
alt?: string;
|
|
131
|
-
tooltip?: string;
|
|
132
|
-
modelValue: number;
|
|
133
|
-
min?: number;
|
|
134
|
-
max?: number;
|
|
135
|
-
showButton?: boolean;
|
|
136
|
-
iconClass?: string | Record<string, boolean>;
|
|
137
|
-
getPercentageClass?: (percentage: number) => string;
|
|
138
|
-
[key: string]: any;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
export interface SwitchToggleProps {
|
|
142
|
-
isOn: boolean;
|
|
143
|
-
disabled?: boolean;
|
|
144
|
-
size?: "small" | "medium";
|
|
145
|
-
activeClass?: string;
|
|
146
|
-
inactiveClass?: string;
|
|
147
|
-
customClass?: string;
|
|
148
|
-
[key: string]: any;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
/* Tray */
|
|
152
|
-
export interface TrayIconButtonProps {
|
|
153
|
-
icon: string;
|
|
154
|
-
alt?: string;
|
|
155
|
-
tooltip?: string;
|
|
156
|
-
badge?: number | null;
|
|
157
|
-
iconClass?: string | Record<string, boolean>;
|
|
158
|
-
customClass?: string | Record<string, boolean>;
|
|
159
|
-
tooltipClass?: string | Record<string, boolean>;
|
|
160
|
-
showCustomTooltip?: boolean;
|
|
161
|
-
customTooltipText?: string;
|
|
162
|
-
[key: string]: any;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/* Component exports */
|
|
166
|
-
export const WindowFrame: DefineComponent<WindowFrameProps, any, any, {}, {}, {}, {}, {}, string>;
|
|
167
|
-
export const SideBar: DefineComponent<SideBarProps, any, any, {}, {}, {}, {}, { 'update:modelValue': (value: string) => void; 'update:collapsed': (value: boolean) => void; change: (value: string) => void }, string>;
|
|
168
|
-
export const SideButton: DefineComponent<SideButtonProps, any, any, {}, {}, {}, {}, { click: () => void }, string>;
|
|
169
|
-
export const SideGroup: DefineComponent<SideGroupProps, any, any, {}, {}, {}, {}, {}, string>;
|
|
170
|
-
|
|
171
|
-
export const ActionButton: DefineComponent<ActionButtonProps, any, any, {}, {}, {}, {}, { click: () => void }, string>;
|
|
172
|
-
export const ToggleControl: DefineComponent<ToggleControlProps, any, any, {}, {}, {}, {}, { click: () => void }, string>;
|
|
173
|
-
export const ConfigSection: DefineComponent<ConfigSectionProps, any, any, {}, {}, {}, {}, {}, string>;
|
|
174
|
-
|
|
175
|
-
export const DeviceCard: DefineComponent<DeviceCardProps, any, any, {}, {}, {}, {}, { action: () => void; click: () => void }, string>;
|
|
176
|
-
export const ListCard: DefineComponent<ListCardProps, any, any, {}, {}, {}, {}, { click: () => void }, string>;
|
|
177
|
-
|
|
178
|
-
export const FormGroup: DefineComponent<FormGroupProps, any, any, {}, {}, {}, {}, {}, string>;
|
|
179
|
-
export const SliderControl: DefineComponent<SliderControlProps, any, any, {}, {}, {}, {}, { 'update:modelValue': (value: number) => void; buttonClick: () => void }, string>;
|
|
180
|
-
export const SwitchToggle: DefineComponent<SwitchToggleProps, any, any, {}, {}, {}, {}, { toggle: (value: boolean) => void }, string>;
|
|
181
|
-
|
|
182
|
-
export const TrayIconButton: DefineComponent<TrayIconButtonProps, any, any, {}, {}, {}, {}, { click: () => void }, string>;
|
|
183
|
-
|
|
184
|
-
// Plugin de Vue
|
|
185
|
-
export interface VueLibVasakPlugin {
|
|
186
|
-
install(app: App): void;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
declare const plugin: VueLibVasakPlugin;
|
|
190
|
-
export default plugin;
|
|
191
|
-
}
|
package/src/window/TopBar.vue
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
<script lang="ts" setup>
|
|
2
|
-
import { ref, Ref, onMounted } from "vue";
|
|
3
|
-
import { getCurrentWindow } from "@tauri-apps/api/window";
|
|
4
|
-
import { getIconSource } from "@vasakgroup/plugin-vicons";
|
|
5
|
-
|
|
6
|
-
const bar = ref(null);
|
|
7
|
-
const appWindow = getCurrentWindow();
|
|
8
|
-
const closeIcon: Ref<string> = ref("");
|
|
9
|
-
const minimizeIcon: Ref<string> = ref("");
|
|
10
|
-
const maximizeIcon: Ref<string> = ref("");
|
|
11
|
-
|
|
12
|
-
const props = defineProps({
|
|
13
|
-
title: String,
|
|
14
|
-
image: String,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
onMounted(async () => {
|
|
18
|
-
closeIcon.value = await getIconSource("window-close");
|
|
19
|
-
minimizeIcon.value = await getIconSource("window-minimize");
|
|
20
|
-
maximizeIcon.value = await getIconSource("window-maximize");
|
|
21
|
-
});
|
|
22
|
-
</script>
|
|
23
|
-
|
|
24
|
-
<template>
|
|
25
|
-
<div
|
|
26
|
-
data-tauri-drag-region
|
|
27
|
-
class="flex h-8 px-4 py-1 bg-primary dark:bg-primary-dark rounded-t-corner-window justify-between align-center"
|
|
28
|
-
ref="bar"
|
|
29
|
-
>
|
|
30
|
-
<div data-tauri-drag-region>
|
|
31
|
-
<img :src="props.image" data-tauri-drag-region class="h-6 w-auto" :alt="props.title" />
|
|
32
|
-
</div>
|
|
33
|
-
<div data-tauri-drag-region>{{ props.title }}</div>
|
|
34
|
-
<div data-tauri-drag-region>
|
|
35
|
-
<span class="win-button" @click="appWindow.minimize()">
|
|
36
|
-
<img :src="minimizeIcon" class="h-6 w-6 inline-block" alt="Minimize">
|
|
37
|
-
</span>
|
|
38
|
-
<span class="win-button" @click="appWindow.toggleMaximize()">
|
|
39
|
-
<img :src="maximizeIcon" class="h-6 w-6 inline-block" alt="Maximize">
|
|
40
|
-
</span>
|
|
41
|
-
<span class="win-button" @click="appWindow.close()">
|
|
42
|
-
<img :src="closeIcon" class="h-6 w-6 inline-block" alt="Close">
|
|
43
|
-
</span>
|
|
44
|
-
</div>
|
|
45
|
-
</div>
|
|
46
|
-
</template>
|