quasar-ui-danx 0.3.22 → 0.4.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.
Files changed (46) hide show
  1. package/.eslintrc.cjs +32 -30
  2. package/danx-local.sh +1 -1
  3. package/dist/danx.es.js +7490 -7519
  4. package/dist/danx.es.js.map +1 -1
  5. package/dist/danx.umd.js +5 -5
  6. package/dist/danx.umd.js.map +1 -1
  7. package/dist/style.css +1 -1
  8. package/package.json +1 -1
  9. package/src/components/ActionTable/ActionMenu.vue +1 -1
  10. package/src/components/ActionTable/ActionTable.vue +64 -45
  11. package/src/components/ActionTable/{ActionTableColumn.vue → Columns/ActionTableColumn.vue} +4 -3
  12. package/src/components/ActionTable/{ActionTableHeaderColumn.vue → Columns/ActionTableHeaderColumn.vue} +2 -2
  13. package/src/components/ActionTable/Columns/index.ts +2 -0
  14. package/src/components/ActionTable/Filters/CollapsableFiltersSidebar.vue +22 -21
  15. package/src/components/ActionTable/Form/Fields/DateRangeField.vue +3 -5
  16. package/src/components/ActionTable/Form/Fields/FileUploadButton.vue +33 -34
  17. package/src/components/ActionTable/Form/Fields/TextField.vue +36 -36
  18. package/src/components/ActionTable/Form/RenderedForm.vue +137 -112
  19. package/src/components/ActionTable/Form/form.d.ts +31 -0
  20. package/src/components/ActionTable/Layouts/ActionTableLayout.vue +88 -4
  21. package/src/components/ActionTable/TableSummaryRow.vue +4 -4
  22. package/src/components/ActionTable/Toolbars/ActionToolbar.vue +46 -0
  23. package/src/components/ActionTable/Toolbars/index.ts +1 -0
  24. package/src/components/ActionTable/index.ts +1 -2
  25. package/src/components/ActionTable/listControls.ts +512 -385
  26. package/src/components/ActionTable/listHelpers.ts +46 -44
  27. package/src/components/PanelsDrawer/PanelsDrawer.vue +37 -26
  28. package/src/components/PanelsDrawer/PanelsDrawerPanels.vue +1 -1
  29. package/src/components/PanelsDrawer/PanelsDrawerTabs.vue +1 -6
  30. package/src/components/Utility/Buttons/ExportButton.vue +1 -1
  31. package/src/components/Utility/Buttons/RefreshButton.vue +5 -5
  32. package/src/components/Utility/Controls/PreviousNextControls.vue +4 -4
  33. package/src/components/Utility/Layouts/CollapsableSidebar.vue +2 -8
  34. package/src/components/Utility/Popovers/PopoverMenu.vue +3 -3
  35. package/src/helpers/actions.ts +197 -187
  36. package/src/styles/general.scss +12 -11
  37. package/src/styles/quasar-reset.scss +59 -11
  38. package/src/styles/themes/danx/action-table.scss +19 -0
  39. package/src/styles/themes/danx/buttons.scss +13 -0
  40. package/src/styles/themes/danx/forms.scss +5 -0
  41. package/src/styles/themes/danx/index.scss +3 -0
  42. package/src/styles/themes/danx/panels.scss +19 -0
  43. package/src/styles/themes/danx/sidebar.scss +3 -0
  44. package/src/styles/themes/danx/toolbar.scss +3 -0
  45. package/types/index.d.ts +1 -0
  46. package/src/styles/actions.scss +0 -10
@@ -2,32 +2,34 @@ import { useDebounceFn } from "@vueuse/core";
2
2
  import { Ref, shallowRef, VNode } from "vue";
3
3
  import { FlashMessages } from "./FlashMessages";
4
4
 
5
+ export type AnyObject = { [key: string]: any };
6
+
5
7
  export type ActionTargetItem = {
6
- id: number | string;
7
- isSaving: Ref<boolean>;
8
- [key: string]: any;
8
+ id: number | string;
9
+ isSaving: Ref<boolean>;
10
+ [key: string]: any;
9
11
  };
10
12
  export type ActionTarget = ActionTargetItem[] | ActionTargetItem;
11
13
 
12
14
  export interface ActionOptions {
13
- name?: string;
14
- label?: string;
15
- menu?: boolean;
16
- batch?: boolean;
17
- category?: string;
18
- class?: string;
19
- debounce?: number;
20
- trigger?: (target: ActionTarget, input: any) => Promise<any>;
21
- vnode?: (target: ActionTarget) => VNode;
22
- enabled?: (target: object) => boolean;
23
- batchEnabled?: (targets: object[]) => boolean;
24
- optimistic?: (action: ActionOptions, target: object, input: any) => void;
25
- onAction?: (action: string | null | undefined, target: object, input: any) => Promise<any>;
26
- onBatchAction?: (action: string | null | undefined, targets: object[], input: any) => Promise<any>;
27
- onStart?: (action: ActionOptions | null, targets: ActionTarget, input: any) => boolean;
28
- onSuccess?: (result: any, targets: ActionTarget, input: any) => any;
29
- onError?: (result: any, targets: ActionTarget, input: any) => any;
30
- onFinish?: (result: any, targets: ActionTarget, input: any) => any;
15
+ name?: string;
16
+ label?: string;
17
+ menu?: boolean;
18
+ batch?: boolean;
19
+ category?: string;
20
+ class?: string;
21
+ debounce?: number;
22
+ trigger?: (target: ActionTarget, input: any) => Promise<any>;
23
+ vnode?: ((target: ActionTarget) => VNode) | undefined;
24
+ enabled?: (target: object) => boolean;
25
+ batchEnabled?: (targets: object[]) => boolean;
26
+ optimistic?: (action: ActionOptions, target: object, input: any) => void;
27
+ onAction?: (action: string | null | undefined, target: object, input: any) => Promise<any>;
28
+ onBatchAction?: (action: string | null | undefined, targets: object[], input: any) => Promise<any>;
29
+ onStart?: (action: ActionOptions | null, targets: ActionTarget, input: any) => boolean;
30
+ onSuccess?: (result: any, targets: ActionTarget, input: any) => any;
31
+ onError?: (result: any, targets: ActionTarget, input: any) => any;
32
+ onFinish?: (result: any, targets: ActionTarget, input: any) => any;
31
33
  }
32
34
 
33
35
  export const activeActionVnode: Ref = shallowRef(null);
@@ -40,173 +42,181 @@ export const activeActionVnode: Ref = shallowRef(null);
40
42
  * @param {ActionOptions | null} globalOptions
41
43
  */
42
44
  export function useActions(actions: ActionOptions[], globalOptions: ActionOptions | null = null) {
43
- const mappedActions = actions.map(action => {
44
- const mappedAction: ActionOptions = { ...globalOptions, ...action };
45
- if (mappedAction.debounce) {
46
- mappedAction.trigger = useDebounceFn((target, input) => performAction(mappedAction, target, input, true), mappedAction.debounce);
47
- } else if (!mappedAction.trigger) {
48
- mappedAction.trigger = (target, input) => performAction(mappedAction, target, input, true);
49
- }
50
- return mappedAction;
51
- });
52
-
53
- /**
54
- * Set the reactive saving state of a target
55
- */
56
- function setTargetSavingState(target: ActionTarget, saving: boolean) {
57
- if (Array.isArray(target)) {
58
- for (const t of target) {
59
- t.isSaving.value = saving;
60
- }
61
- } else {
62
- target.isSaving.value = saving;
63
- }
64
- }
65
-
66
- /**
67
- * Perform an action on a set of targets
68
- *
69
- * @param {string} name - can either be a string or an action object
70
- * @param {object[]|object} target - an array of targets or a single target object
71
- * @param {any} input - The input data to pass to the action handler
72
- * @param isTriggered - Whether the action was triggered by a trigger function
73
- */
74
- async function performAction(name: string | object, target: ActionTarget, input: any = null, isTriggered = false) {
75
- const action: ActionOptions | null | undefined = typeof name === "string" ? mappedActions.find(a => a.name === name) : name;
76
- if (!action) {
77
- throw new Error(`Unknown action: ${name}`);
78
- }
79
-
80
- // We always want to call the trigger function if it exists, unless it's already been triggered
81
- // This provides behavior like debounce and custom action resolution
82
- if (action.trigger && !isTriggered) {
83
- return action.trigger(target, input);
84
- }
85
-
86
- const vnode = action.vnode && action.vnode(target);
87
- let result: any;
88
-
89
- // Run the onStart handler if it exists and quit the operation if it returns false
90
- if (action.onStart) {
91
- if (!action.onStart(action, target, input)) {
92
- return;
93
- }
94
- }
95
-
96
- setTargetSavingState(target, true);
97
-
98
- // If additional input is required, first render the vnode and wait for the confirm or cancel action
99
- if (vnode) {
100
- // If the action requires an input, we set the activeActionVnode to the input component.
101
- // This will tell the ActionVnode to render the input component, and confirm or cancel the
102
- // action The confirm function has the input from the component passed and will resolve the promise
103
- // with the result of the action
104
- result = await new Promise((resolve, reject) => {
105
- activeActionVnode.value = {
106
- vnode,
107
- confirm: async (confirmInput: any) => {
108
- const result = await onConfirmAction(action, target, { ...input, ...confirmInput });
109
-
110
- // Only resolve when we have a non-error response, so we can show the error message w/o
111
- // hiding the dialog / vnode
112
- if (result === undefined || result === true || result?.success) {
113
- resolve(result);
114
- }
115
- },
116
- cancel: resolve
117
- };
118
- });
119
-
120
- activeActionVnode.value = null;
121
- } else {
122
- result = await onConfirmAction(action, target, input);
123
- }
124
-
125
- setTargetSavingState(target, false);
126
-
127
- return result;
128
- }
129
-
130
- /**
131
- * Filter the list of actions based on the provided filters in key-value pairs
132
- * You can filter on any ActionOptions property by matching the value exactly or by providing an array of values
133
- *
134
- * @param filters
135
- * @returns {ActionOptions[]}
136
- */
137
- function filterActions(filters: object): ActionOptions[] {
138
- let filteredActions = [...mappedActions];
139
-
140
- for (const filter of Object.keys(filters)) {
141
- const filterValue = filters[filter];
142
- filteredActions = filteredActions.filter((a: object) => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
143
- }
144
- return filteredActions;
145
- }
146
-
147
- return {
148
- actions: mappedActions,
149
- filterActions,
150
- performAction
151
- };
45
+ const mappedActions = actions.map(action => {
46
+ const mappedAction: ActionOptions = { ...globalOptions, ...action };
47
+ if (mappedAction.debounce) {
48
+ mappedAction.trigger = useDebounceFn((target, input) => performAction(mappedAction, target, input, true), mappedAction.debounce);
49
+ } else if (!mappedAction.trigger) {
50
+ mappedAction.trigger = (target, input) => performAction(mappedAction, target, input, true);
51
+ }
52
+ return mappedAction;
53
+ });
54
+
55
+ /**
56
+ * Set the reactive saving state of a target
57
+ */
58
+ function setTargetSavingState(target: ActionTarget, saving: boolean) {
59
+ if (Array.isArray(target)) {
60
+ for (const t of target) {
61
+ t.isSaving.value = saving;
62
+ }
63
+ } else {
64
+ target.isSaving.value = saving;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Perform an action on a set of targets
70
+ *
71
+ * @param {string} name - can either be a string or an action object
72
+ * @param {object[]|object} target - an array of targets or a single target object
73
+ * @param {any} input - The input data to pass to the action handler
74
+ * @param isTriggered - Whether the action was triggered by a trigger function
75
+ */
76
+ async function performAction(name: string | object, target: ActionTarget, input: any = null, isTriggered = false) {
77
+ const action: ActionOptions | null | undefined = typeof name === "string" ? mappedActions.find(a => a.name === name) : name;
78
+ if (!action) {
79
+ throw new Error(`Unknown action: ${name}`);
80
+ }
81
+
82
+ // We always want to call the trigger function if it exists, unless it's already been triggered
83
+ // This provides behavior like debounce and custom action resolution
84
+ if (action.trigger && !isTriggered) {
85
+ return action.trigger(target, input);
86
+ }
87
+
88
+ const vnode = action.vnode && action.vnode(target);
89
+ let result: any;
90
+
91
+ // Run the onStart handler if it exists and quit the operation if it returns false
92
+ if (action.onStart) {
93
+ if (!action.onStart(action, target, input)) {
94
+ return;
95
+ }
96
+ }
97
+
98
+ setTargetSavingState(target, true);
99
+
100
+ // If additional input is required, first render the vnode and wait for the confirm or cancel action
101
+ if (vnode) {
102
+ // If the action requires an input, we set the activeActionVnode to the input component.
103
+ // This will tell the ActionVnode to render the input component, and confirm or cancel the
104
+ // action The confirm function has the input from the component passed and will resolve the promise
105
+ // with the result of the action
106
+ result = await new Promise((resolve) => {
107
+ activeActionVnode.value = {
108
+ vnode,
109
+ confirm: async (confirmInput: any) => {
110
+ const result = await onConfirmAction(action, target, { ...input, ...confirmInput });
111
+
112
+ // Only resolve when we have a non-error response, so we can show the error message w/o
113
+ // hiding the dialog / vnode
114
+ if (result === undefined || result === true || result?.success) {
115
+ resolve(result);
116
+ }
117
+ },
118
+ cancel: resolve
119
+ };
120
+ });
121
+
122
+ activeActionVnode.value = null;
123
+ } else {
124
+ result = await onConfirmAction(action, target, input);
125
+ }
126
+
127
+ setTargetSavingState(target, false);
128
+
129
+ return result;
130
+ }
131
+
132
+ /**
133
+ * Filter the list of actions based on the provided filters in key-value pairs
134
+ * You can filter on any ActionOptions property by matching the value exactly or by providing an array of values
135
+ *
136
+ * @param filters
137
+ * @returns {ActionOptions[]}
138
+ */
139
+ function filterActions(filters: AnyObject): ActionOptions[] {
140
+ let filteredActions = [...mappedActions];
141
+
142
+ for (const filter of Object.keys(filters)) {
143
+ const filterValue = filters[filter];
144
+ filteredActions = filteredActions.filter((a: AnyObject) => a[filter] === filterValue || (Array.isArray(filterValue) && filterValue.includes(a[filter])));
145
+ }
146
+ return filteredActions;
147
+ }
148
+
149
+ return {
150
+ actions: mappedActions,
151
+ filterActions,
152
+ performAction
153
+ };
152
154
  }
153
155
 
154
156
  async function onConfirmAction(action: ActionOptions, target: ActionTarget, input: any = null) {
155
- if (!action.onAction) {
156
- throw new Error("No onAction handler found for the selected action:" + action.name);
157
- }
158
-
159
- let result: any;
160
- try {
161
- if (Array.isArray(target)) {
162
- if (action.onBatchAction) {
163
- result = await action.onBatchAction(action.name, target, input);
164
- } else {
165
- result = { error: `Action ${action.name} does not support batch actions` };
166
- }
167
- } else {
168
- // If the action has an optimistic callback, we call it before the actual action to immediately
169
- // update the UI
170
- if (action.optimistic) {
171
- action.optimistic(action, target, input);
172
- }
173
-
174
- result = await action.onAction(action.name, target, input);
175
- }
176
- } catch (e) {
177
- console.error(e);
178
- result = { error: `An error occurred while performing the action ${action.label}. Please try again later.` };
179
- }
180
-
181
- // If there is no return value or the result marks it as successful, we show a success message
182
- if (result === undefined || result === true || result?.success) {
183
- if (result?.success && Array.isArray(target)) {
184
- FlashMessages.success(`Successfully performed action ${action.label} on ${target.length} items`);
185
- }
186
-
187
- if (action.onSuccess) {
188
- action.onSuccess(result, target, input);
189
- }
190
- } else {
191
- const errors = [];
192
- if (result.errors) {
193
- errors.push(...result.errors);
194
- } else if (result.error) {
195
- errors.push(typeof result.error === "string" ? result.error : result.error.message);
196
- } else {
197
- errors.push("An unknown error occurred. Please try again later.");
198
- }
199
-
200
- FlashMessages.combine("error", errors);
201
-
202
- if (action.onError) {
203
- action.onError(result, target, input);
204
- }
205
- }
206
-
207
- if (action.onFinish) {
208
- action.onFinish(result, target, input);
209
- }
210
-
211
- return result;
157
+ if (!action.onAction) {
158
+ throw new Error("No onAction handler found for the selected action:" + action.name);
159
+ }
160
+
161
+ let result: any;
162
+ try {
163
+ if (Array.isArray(target)) {
164
+ if (action.onBatchAction) {
165
+ result = await action.onBatchAction(action.name, target, input);
166
+ } else {
167
+ result = { error: `Action ${action.name} does not support batch actions` };
168
+ }
169
+ } else {
170
+ // If the action has an optimistic callback, we call it before the actual action to immediately
171
+ // update the UI
172
+ if (action.optimistic) {
173
+ action.optimistic(action, target, input);
174
+ }
175
+
176
+ result = await action.onAction(action.name, target, input);
177
+ }
178
+ } catch (e) {
179
+ console.error(e);
180
+ result = { error: `An error occurred while performing the action ${action.label}. Please try again later.` };
181
+ }
182
+
183
+ // If there is no return value or the result marks it as successful, we show a success message
184
+ if (result === undefined || result === true || result?.success) {
185
+ if (result?.success && Array.isArray(target)) {
186
+ FlashMessages.success(`Successfully performed action ${action.label} on ${target.length} items`);
187
+ }
188
+
189
+ if (action.onSuccess) {
190
+ action.onSuccess(result, target, input);
191
+ }
192
+ } else {
193
+ const errors = [];
194
+ if (result.errors) {
195
+ errors.push(...result.errors);
196
+ } else if (result.error) {
197
+ let message = result.error;
198
+ if (typeof result.error === "boolean") {
199
+ message = result.message;
200
+ } else if (typeof result.error === "object") {
201
+ message = result.error.message;
202
+ } else if (typeof result.error !== "string") {
203
+ message = "An unknown error occurred. Please try again later.";
204
+ }
205
+ errors.push(message);
206
+ } else {
207
+ errors.push("An unexpected error occurred. Please try again later.");
208
+ }
209
+
210
+ FlashMessages.combine("error", errors);
211
+
212
+ if (action.onError) {
213
+ action.onError(result, target, input);
214
+ }
215
+ }
216
+
217
+ if (action.onFinish) {
218
+ action.onFinish(result, target, input);
219
+ }
220
+
221
+ return result;
212
222
  }
@@ -1,18 +1,19 @@
1
1
  body {
2
- height: 100vh;
2
+ height: 100vh;
3
+ width: 100vw;
3
4
 
4
- #app,
5
- #main-layout,
6
- main {
7
- height: 100%;
8
- }
5
+ #app,
6
+ #main-layout,
7
+ main {
8
+ height: 100%;
9
+ }
9
10
  }
10
11
 
11
12
  a {
12
- cursor: pointer;
13
+ cursor: pointer;
13
14
 
14
- &.is-disabled {
15
- opacity: 0.5;
16
- cursor: not-allowed;
17
- }
15
+ &.is-disabled {
16
+ opacity: 0.5;
17
+ cursor: not-allowed;
18
+ }
18
19
  }
@@ -1,9 +1,66 @@
1
- .q-tab {
2
- text-transform: capitalize
1
+ .danx-app {
2
+ .q-tab {
3
+ text-transform: capitalize
4
+ }
5
+
6
+ .q-table__card {
7
+ background: inherit;
8
+ color: inherit;
9
+ }
10
+
11
+ .q-checkbox__inner {
12
+ color: inherit;
13
+ }
14
+
15
+ .q-toolbar {
16
+ min-height: 0;
17
+ padding: 0;
18
+ }
19
+
20
+ .q-notification__actions {
21
+ color: inherit;
22
+ }
23
+
24
+ .q-date {
25
+ min-width: 100px;
26
+
27
+ .q-date__view {
28
+ min-height: 0;
29
+ }
30
+ }
31
+
32
+ .q-field {
33
+ &.q-field--auto-height {
34
+ .q-field__control {
35
+ min-height: 0;
36
+
37
+ .q-field__native {
38
+ min-height: 0;
39
+ color: inherit;
40
+ }
41
+ }
42
+
43
+ }
44
+
45
+ &.q-field--labeled {
46
+ .q-field__control-container {
47
+ padding-top: 1.1rem;
48
+ }
49
+ }
50
+
51
+ .q-field__marginal, .q-field__input, .q-field__label {
52
+ color: inherit;
53
+ }
54
+ }
55
+ }
56
+
57
+ .q-item {
58
+ min-height: 0;
3
59
  }
4
60
 
5
61
  .q-tab-panels {
6
62
  overflow: visible;
63
+ background: inherit;
7
64
 
8
65
  .q-panel {
9
66
  overflow: visible;
@@ -21,12 +78,3 @@
21
78
  overflow-y: auto;
22
79
  }
23
80
  }
24
-
25
- .q-toolbar {
26
- min-height: 0;
27
- padding: 0;
28
- }
29
-
30
- .q-notification__actions {
31
- color: inherit;
32
- }
@@ -0,0 +1,19 @@
1
+ .dx-table-summary-tr {
2
+ @apply bg-gray-100;
3
+
4
+ &.has-selection {
5
+ @apply bg-blue-600 text-white;
6
+ }
7
+
8
+ &.is-loading {
9
+ @apply opacity-50;
10
+ }
11
+
12
+ .dx-table-summary-td {
13
+ @apply font-bold bg-gray-100 pl-5;
14
+
15
+ &.has-selection {
16
+ @apply bg-blue-600 text-white pl-4;
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,13 @@
1
+ .dx-action-button {
2
+ @apply bg-slate-200 transition-all cursor-pointer rounded outline outline-transparent;
3
+
4
+ &.dx-column-action-menu:hover {
5
+ @apply bg-blue-200 outline-blue-600;
6
+ }
7
+ }
8
+
9
+ .dx-previous-next-controls {
10
+ .dx-control {
11
+ @apply bg-slate-200 border-gray-300;
12
+ }
13
+ }
@@ -0,0 +1,5 @@
1
+ .q-field {
2
+ .q-field__label {
3
+ @apply text-sm text-gray-700;
4
+ }
5
+ }
@@ -0,0 +1,3 @@
1
+ @import "action-table";
2
+ @import "buttons";
3
+ @import "toolbar";
@@ -0,0 +1,19 @@
1
+ .dx-panels-drawer-header {
2
+ @apply border-b;
3
+ }
4
+
5
+ .dx-panels-drawer-panels {
6
+ @apply bg-gray-100
7
+ }
8
+
9
+ .dx-panels-drawer-tabs {
10
+ @apply bg-gray-200 border-r;
11
+
12
+ .q-tab {
13
+ @apply text-left py-2.5 px-2 rounded-lg hover:bg-slate-200;
14
+
15
+ &.q-tab--active {
16
+ @apply text-white bg-blue-600;
17
+ }
18
+ }
19
+ }
@@ -0,0 +1,3 @@
1
+ .dx-collapsable-sidebar {
2
+ @apply border-r border-gray-200
3
+ }
@@ -0,0 +1,3 @@
1
+ .dx-batch-actions {
2
+ @apply px-4 bg-slate-600 rounded text-red-500;
3
+ }
@@ -0,0 +1 @@
1
+ export * from "../src/components/ActionTable/Form/form.d.ts";
@@ -1,10 +0,0 @@
1
- .action-button {
2
- cursor: pointer;
3
- border-radius: 0.5em;
4
- transition: all 0.5s;
5
- outline: 1px solid transparent;
6
-
7
- &:hover {
8
- @apply bg-blue-200 outline outline-blue-600;
9
- }
10
- }