Skip to content

Commit eb48315

Browse files
committed
feat: new BaseErrorBoundary component
BREAKING CHANGE: form make expects a function with optional payload
1 parent 96fcf25 commit eb48315

File tree

13 files changed

+426
-315
lines changed

13 files changed

+426
-315
lines changed

packages/common-enums/src/components.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export const componentNames = [
88
"BaseInput",
99
"BaseSelect",
1010
"BaseWrapper",
11+
"BaseErrorBoundary",
1112
// icon
1213
"Icon",
1314
"IconFa",

packages/common-helpers/src/form/input.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ export class FormInputDefault<T extends eFormTypeSimple | eFormTypeComplex = eFo
7575
public readonly placeholder!: string;
7676
public readonly icon!: tFormIcon;
7777
public readonly autocomplete!: tFormAutocomplete;
78-
public readonly min!: number;
79-
public readonly max!: number;
78+
public min!: number;
79+
public max!: number;
8080

8181
constructor(
8282
formInput: iFormInputDefault<T>,

packages/common-helpers/src/locale/en.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const localeBase: iLocaleBase = {
3737
delete_all: "Delete all",
3838
pick: "Pick",
3939
refresh: "Refresh",
40+
render_error: "Couldn't render the contents due to an unknown error",
4041
swal: {
4142
cancel: "Cancel",
4243
continue: "Continue",
@@ -150,6 +151,8 @@ export const localeTable: iLocaleTable = {
150151
table_duplicate: "Duplicate",
151152
table_options: "Options",
152153
table_open_url: "Open URL",
154+
table_hide_all: "Hide all",
155+
table_show_all: "Show all",
153156
swal: {
154157
table_delete_node_title: "Are you sure you want to delete this element?",
155158
table_delete_node_disclaimer:

packages/common-helpers/src/locale/es.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const localeBase: iLocaleBase = {
3737
delete_all: "Eliminar todo",
3838
pick: "Elegir",
3939
refresh: "Refrescar",
40+
render_error: "No pudimos renderizar los contenidos debido a un error desconocido",
4041
swal: {
4142
cancel: "Cancelar",
4243
continue: "Continuar",
@@ -151,6 +152,8 @@ export const localeTable: iLocaleTable = {
151152
table_duplicate: "Duplicar",
152153
table_options: "Opciones",
153154
table_open_url: "Abrir enlace",
155+
table_hide_all: "Ocultar todo",
156+
table_show_all: "Mostrar todo",
154157
swal: {
155158
table_delete_node_title: "¿Estas seguro de que quieres eliminar este elemento?",
156159
table_delete_node_disclaimer:

packages/common-types/src/locale.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ export interface iLocaleBase {
4646
pick: string;
4747
/** @example "Refresh" */
4848
refresh: string;
49+
/** @example "Couldn't render the contents due to an unknown error" */
50+
render_error: string;
4951
swal: {
5052
/** @example "Cancel" */
5153
cancel: string;
@@ -227,6 +229,10 @@ export interface iLocaleTable {
227229
table_options: string;
228230
/** @example "Open URL" */
229231
table_open_url: string;
232+
/** @example "Hide all" */
233+
table_hide_all: string;
234+
/** @example "Show all" */
235+
table_show_all: string;
230236
swal: {
231237
/** @example "Are you sure you want to delete this element?" */
232238
table_delete_node_title: string;

packages/components-vue/src/components/Modal.vue

Lines changed: 92 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,102 @@
11
<template>
2-
<slot v-if="$slots.toggle" name="toggle" v-bind="{ toggleModal, model }"></slot>
3-
<BaseWrapper v-if="!disabled" :key="modalId" :el="Teleport" :wrap="!!target" :to="target">
4-
<dialog :id="modalId" ref="modalRef" @close="closeModal" @mousedown="clickOutside">
5-
<div
6-
v-show="!loading && !hide"
7-
class="modal"
8-
role="document"
9-
:class="[modalClass ?? 'flx --flxColumn --flx-start-stretch --width', themeClasses]"
10-
v-bind="$attrs"
11-
>
12-
<slot
13-
name="modal-header"
14-
v-bind="{ toggleModal, model, invertedTheme: invertedThemeValues }"
2+
<BaseErrorBoundary :theme="theme">
3+
<slot v-if="$slots.toggle" name="toggle" v-bind="{ toggleModal, model }"></slot>
4+
<BaseWrapper v-if="!disabled" :key="modalId" :el="Teleport" :wrap="!!target" :to="target">
5+
<dialog :id="modalId" ref="modalRef" @close="closeModal" @mousedown="clickOutside">
6+
<!-- v-show is used so the slot can run any required task for the modal to be enabled -->
7+
<div
8+
v-show="!loading && !hide"
9+
class="modal"
10+
role="document"
11+
:class="[
12+
modalClass ?? 'flx --flxColumn --flx-start-stretch --width',
13+
themeClasses,
14+
]"
15+
v-bind="$attrs"
1516
>
16-
<div v-if="title" class="flx --flxRow --flx-between-center">
17-
<div class="txt --gaping-none">
18-
<h5>{{ title }}</h5>
19-
<p v-if="subtitle" class="--txtSize-xs">{{ subtitle }}</p>
17+
<slot
18+
name="modal-header"
19+
v-bind="{ toggleModal, model, invertedTheme: invertedThemeValues }"
20+
>
21+
<div v-if="title" class="flx --flxRow --flx-between-center">
22+
<div class="txt --gaping-none">
23+
<h5>{{ title }}</h5>
24+
<p v-if="subtitle" class="--txtSize-xs">{{ subtitle }}</p>
25+
</div>
26+
<ActionLink
27+
:theme="invertedThemeValues"
28+
:aria-label="cancelButtonOptions.title"
29+
@click.stop="closeModal()"
30+
>
31+
<IconFa name="xmark" :size="20" />
32+
</ActionLink>
2033
</div>
21-
<ActionLink
22-
:theme="invertedThemeValues"
23-
:aria-label="cancelButtonOptions.title"
24-
@click.stop="closeModal()"
25-
>
26-
<IconFa name="xmark" :size="20" />
27-
</ActionLink>
34+
</slot>
35+
<div class="scroll --vertical">
36+
<!-- Main modal content -->
37+
<slot
38+
v-bind="{ toggleModal, model, invertedTheme: invertedThemeValues }"
39+
></slot>
2840
</div>
29-
</slot>
30-
<div class="scroll --vertical">
31-
<!-- Main modal content -->
3241
<slot
42+
name="modal-footer"
3343
v-bind="{ toggleModal, model, invertedTheme: invertedThemeValues }"
34-
></slot>
35-
</div>
36-
<slot
37-
name="modal-footer"
38-
v-bind="{ toggleModal, model, invertedTheme: invertedThemeValues }"
39-
>
40-
<div v-if="!hideFooter" class="flx --flxRow --flx-end-center">
41-
<ActionButton
42-
v-if="saveButtonOptions.visible"
43-
:theme="invertedThemeValues"
44-
:aria-label="saveButtonOptions.title"
45-
:class="saveButtonOptions.btnClass"
46-
@click="emit('save', closeModal, $event)"
47-
>
48-
{{ saveButtonOptions.title }}
49-
</ActionButton>
50-
<ActionButtonToggle
51-
v-if="cancelButtonOptions.visible"
52-
:theme="invertedThemeValues"
53-
:aria-label="cancelButtonOptions.title"
54-
:class="cancelButtonOptions.btnClass"
55-
data-dismiss="modal"
56-
round=":sm-inv"
57-
@click.stop="closeModal()"
58-
>
59-
<IconFa name="xmark" hidden="-full:sm" />
60-
<IconFa name="xmark" regular hidden="-full:sm" />
61-
<span class="--hidden-full:sm-inv">
62-
{{ cancelButtonOptions.title }}
63-
</span>
64-
</ActionButtonToggle>
65-
</div>
66-
</slot>
67-
</div>
68-
<LoaderSimple v-if="loading || hide" :theme="invertedThemeValues">
69-
<transition name="fade">
70-
<div
71-
v-if="loadingTooLong || (props.hide && props.hideMessage)"
72-
class="txt --txtAlignFlx-center --gaping-5"
7344
>
74-
<p class="--txtColor-light --txtShadow --txtSize-sm">
75-
{{ props.hideMessage ? props.hideMessage : t("modal_taking_too_long") }}
76-
</p>
77-
<ActionButton
78-
:theme="invertedThemeValues"
79-
:aria-label="t('close')"
80-
@click="closeModal()"
45+
<div v-if="!hideFooter" class="flx --flxRow --flx-end-center">
46+
<ActionButton
47+
v-if="saveButtonOptions.visible"
48+
:theme="invertedThemeValues"
49+
:aria-label="saveButtonOptions.title"
50+
:class="saveButtonOptions.btnClass"
51+
@click="emit('save', closeModal, $event)"
52+
>
53+
{{ saveButtonOptions.title }}
54+
</ActionButton>
55+
<ActionButtonToggle
56+
v-if="cancelButtonOptions.visible"
57+
:theme="invertedThemeValues"
58+
:aria-label="cancelButtonOptions.title"
59+
:class="cancelButtonOptions.btnClass"
60+
data-dismiss="modal"
61+
round=":sm-inv"
62+
@click.stop="closeModal()"
63+
>
64+
<IconFa name="xmark" hidden="-full:sm" />
65+
<IconFa name="xmark" regular hidden="-full:sm" />
66+
<span class="--hidden-full:sm-inv">
67+
{{ cancelButtonOptions.title }}
68+
</span>
69+
</ActionButtonToggle>
70+
</div>
71+
</slot>
72+
</div>
73+
<LoaderSimple v-if="loading || hide" :theme="theme">
74+
<transition name="fade">
75+
<div
76+
v-if="loadingTooLong || (props.hide && props.hideMessage)"
77+
class="txt --txtAlignFlx-center --gaping-5"
8178
>
82-
{{ t("close") }}
83-
</ActionButton>
84-
</div>
85-
</transition>
86-
</LoaderSimple>
87-
</dialog>
88-
</BaseWrapper>
89-
<slot v-else v-bind="{ toggleModal, model, invertedTheme: invertedThemeValues }"></slot>
79+
<p class="--txtColor-light --txtShadow --txtSize-sm">
80+
{{
81+
props.hideMessage
82+
? props.hideMessage
83+
: t("modal_taking_too_long")
84+
}}
85+
</p>
86+
<ActionButton
87+
:theme="invertedThemeValues"
88+
:aria-label="t('close')"
89+
@click="closeModal()"
90+
>
91+
{{ t("close") }}
92+
</ActionButton>
93+
</div>
94+
</transition>
95+
</LoaderSimple>
96+
</dialog>
97+
</BaseWrapper>
98+
<slot v-else v-bind="{ toggleModal, model, invertedTheme: invertedThemeValues }"></slot>
99+
</BaseErrorBoundary>
90100
</template>
91101

92102
<script setup lang="ts">
@@ -103,6 +113,7 @@
103113
104114
import { useI18n, useSwal } from "@open-xamu-co/ui-common-helpers";
105115
116+
import BaseErrorBoundary from "./base/ErrorBoundary.vue";
106117
import BaseWrapper from "./base/Wrapper.vue";
107118
import IconFa from "./icon/Fa.vue";
108119
import ActionLink from "./action/Link.vue";

packages/components-vue/src/components/Table.vue

Lines changed: 42 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,41 @@
11
<template>
22
<div v-if="nodes.length" class="flx --flxColumn --flx-start-stretch --width">
3-
<div v-if="!isReadOnly" class="flx --flxRow --flx-end-center">
4-
<ActionButton
5-
v-if="!!deleteNode"
6-
:tooltip="t('table_delete')"
7-
tooltip-as-text
8-
tooltip-position="bottom"
9-
:theme="[eColors.DANGER, themeValues[0]]"
10-
:disabled="!selectedNodes.some(([n]) => n)"
11-
@click="deleteNodesAndRefresh"
3+
<div v-if="!isReadOnly || $slots.default" class="flx --flxRow --flx-start-center">
4+
<ActionButtonLink
5+
v-if="$slots.default"
6+
:theme="theme"
7+
:active="openNodesCount === selectedNodes.length"
8+
@click="toggleAll(!(openNodesCount === selectedNodes.length), 1)"
129
>
1310
<span>
1411
{{
15-
selectedNodesCount === selectedNodes.length
16-
? t("delete_all")
17-
: t("delete", selectedNodesCount)
12+
openNodesCount === selectedNodes.length
13+
? t("table_hide_all")
14+
: t("table_show_all")
1815
}}
1916
</span>
20-
<IconFa name="trash-can" />
21-
</ActionButton>
17+
<IconFa class="--indicator" name="chevron-up" />
18+
</ActionButtonLink>
19+
<div v-if="!isReadOnly" class="flx --flxRow --flx-end-center --flx">
20+
<ActionButton
21+
v-if="deleteNode"
22+
:tooltip="t('table_delete')"
23+
tooltip-as-text
24+
tooltip-position="bottom"
25+
:theme="[eColors.DANGER, themeValues[0]]"
26+
:disabled="!selectedNodes.some(([n]) => n)"
27+
@click="deleteNodesAndRefresh"
28+
>
29+
<span>
30+
{{
31+
selectedNodesCount === selectedNodes.length
32+
? t("delete_all")
33+
: t("delete", selectedNodesCount)
34+
}}
35+
</span>
36+
<IconFa name="trash-can" />
37+
</ActionButton>
38+
</div>
2239
</div>
2340
<div v-bind="$attrs" class="scroll --horizontal --always">
2441
<table :id="tableId" class="tbl" :class="themeClasses">
@@ -401,6 +418,10 @@
401418
*/
402419
refresh?: () => unknown;
403420
extraCols?: boolean;
421+
/**
422+
* Default children visibility
423+
*/
424+
childrenVisibility?: boolean;
404425
childrenName?: string;
405426
childrenCountKey?: keyof Ti;
406427
modalTheme?: tThemeTuple | tProp<tThemeModifier>;
@@ -437,7 +458,10 @@
437458
/** [selected, show] */
438459
const selectedNodes = ref<[boolean, boolean][]>(reFillNodes(props.nodes.length));
439460
const selectedNodesCount = computed(() => {
440-
return selectedNodes.value.filter(([n]) => n).length;
461+
return selectedNodes.value.filter(([selected]) => selected).length;
462+
});
463+
const openNodesCount = computed(() => {
464+
return selectedNodes.value.filter(([, open]) => open).length;
441465
});
442466
/**
443467
* ordering property
@@ -507,13 +531,13 @@
507531
});
508532
509533
function reFillNodes(length: number): [boolean, boolean][] {
510-
return Array.from({ length }, () => [false, false]);
534+
return Array.from({ length }, () => [false, !!props.childrenVisibility]);
511535
}
512536
function childrenCount(node: T) {
513537
return props.childrenCountKey ? node[props.childrenCountKey] : 0;
514538
}
515-
function toggleAll(value = true) {
516-
selectedNodes.value.forEach((_, i) => (selectedNodes.value[i][0] = value));
539+
function toggleAll(value = true, index = 0) {
540+
selectedNodes.value.forEach((_, i) => (selectedNodes.value[i][index] = value));
517541
}
518542
function toggleChildren(index: number) {
519543
const [selected, children] = selectedNodes.value[index];

0 commit comments

Comments
 (0)