@nixweb/nixloc-ui 1.0.0 → 1.2.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nixweb/nixloc-ui",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Componentes UI",
5
5
  "author": "Fábio Ávila <fabio@nixweb.com.br>",
6
6
  "private": false,
@@ -41,6 +41,7 @@ export default {
41
41
  if (this.clicked) this.clicked(this.params);
42
42
  },
43
43
  },
44
+
44
45
  };
45
46
  </script>
46
47
  <style scoped>
@@ -33,7 +33,7 @@ export default {
33
33
  };
34
34
  </script>
35
35
 
36
- <style scoped>
36
+ <style scoped>
37
37
  .input {
38
38
  width: 90%;
39
39
  border: none;
@@ -3,7 +3,7 @@
3
3
  <vodal :duration="80" :show="modal.open" @hide="hide()" :width="width" :height="height" :closeOnEsc="closeOnEsc"
4
4
  :closeButton="closeButton" :closeOnClickMask="false">
5
5
  <div class="d-block text-left">
6
- <Messages v-if="!vodal.open" />
6
+ <Messages v-if="!vodal.open && showValidation" />
7
7
  <div class="title">{{ title }}</div>
8
8
  <hr class="hr" />
9
9
  <slot></slot>
@@ -33,6 +33,10 @@ export default {
33
33
  type: Boolean,
34
34
  default: true,
35
35
  },
36
+ showValidation: {
37
+ type: Boolean,
38
+ default: true,
39
+ },
36
40
  onHideModal: Function,
37
41
  },
38
42
  components: { Messages, Vodal },
@@ -0,0 +1,193 @@
1
+ <template>
2
+ <transition name="slide-up">
3
+ <section v-if="isOpen" class="bab-container">
4
+ <header class="bab-header">
5
+ <div class="bab-left">
6
+ <div class="bab-icon-wrapper">
7
+ <i class="fas fa-list-check bab-icon"></i>
8
+ <span class="bab-count">{{ selected.length }}</span>
9
+ </div>
10
+ <strong class="bab-title title">
11
+ <span>selecionados</span>
12
+ </strong>
13
+ </div>
14
+
15
+ <div class="bab-actions">
16
+ <button type="button" class="bab-btn-close" @click="close">
17
+ <i class="fas fa-times"></i>
18
+ </button>
19
+ </div>
20
+ </header>
21
+ <main class="bab-content">
22
+ <slot />
23
+ </main>
24
+ </section>
25
+ </transition>
26
+ </template>
27
+
28
+ <script>
29
+
30
+ import { mapMutations } from "vuex";
31
+
32
+ export default {
33
+ name: "BottomActionsBar",
34
+ props: {
35
+ selected: { type: Array, default: () => [] }
36
+ },
37
+ computed: {
38
+ isOpen() {
39
+ return Array.isArray(this.selected) && this.selected.length > 0;
40
+ }
41
+ },
42
+ mounted() {
43
+ document.addEventListener("keydown", this.onKeydown);
44
+ },
45
+ beforeDestroy() {
46
+ document.removeEventListener("keydown", this.onKeydown);
47
+ },
48
+ methods: {
49
+ ...mapMutations("generic", ["addEvent"]),
50
+ close() {
51
+ this.$emit("close");
52
+ this.addEvent({ name: "deselectAll" });
53
+ },
54
+ onKeydown(e) {
55
+ if (e.key === "Escape" && this.isOpen) this.close();
56
+ }
57
+ }
58
+ };
59
+ </script>
60
+
61
+ <style>
62
+ .bab-container {
63
+ position: fixed;
64
+ left: 0;
65
+ right: 0;
66
+ bottom: 0;
67
+ height: 200px;
68
+
69
+ border-top-left-radius: 18px;
70
+ border-top-right-radius: 18px;
71
+ box-shadow: 0 -8px 30px rgba(0, 0, 0, 0.25);
72
+ z-index: 9999;
73
+ display: flex;
74
+ flex-direction: column;
75
+ animation: elevate 0.2s ease-in-out;
76
+ }
77
+
78
+ .bab-header {
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: space-between;
82
+ padding: 12px 16px;
83
+ border-bottom: 1px solid #eef2f7;
84
+ }
85
+
86
+ .bab-left {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 10px;
90
+ }
91
+
92
+ .bab-icon-wrapper {
93
+ position: relative;
94
+ width: 38px;
95
+ height: 38px;
96
+ border-radius: 50%;
97
+ background: #007bff;
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ color: #fff;
102
+ }
103
+
104
+ .bab-icon {
105
+ font-size: 16px;
106
+ }
107
+
108
+ .bab-count {
109
+ position: absolute;
110
+ top: -5px;
111
+ right: -5px;
112
+ background: #fff;
113
+ color: #007bff;
114
+ font-size: 11px;
115
+ font-weight: bold;
116
+ border-radius: 50%;
117
+ width: 18px;
118
+ height: 18px;
119
+ display: flex;
120
+ align-items: center;
121
+ justify-content: center;
122
+ border: 2px solid #007bff;
123
+ }
124
+
125
+ .bab-title {
126
+ font-size: 14px;
127
+ font-weight: 500;
128
+ color: #0f172a;
129
+ }
130
+
131
+ .bab-actions {
132
+ display: flex;
133
+ align-items: center;
134
+ }
135
+
136
+ .bab-btn-close {
137
+ background: transparent;
138
+ border: none;
139
+ color: #9ca3af;
140
+ font-size: 18px;
141
+ cursor: pointer;
142
+ transition: color 0.2s ease;
143
+ }
144
+
145
+ .bab-btn-close:hover {
146
+ color: #6b7280;
147
+ }
148
+
149
+ .bab-content {
150
+ flex: 1;
151
+ padding: 12px 16px;
152
+ overflow: auto;
153
+ display: grid;
154
+ grid-auto-flow: column;
155
+ grid-auto-columns: minmax(220px, 1fr);
156
+ gap: 12px;
157
+ }
158
+
159
+ .slide-up-enter-active,
160
+ .slide-up-leave-active {
161
+ transition: transform 200ms ease-in-out, opacity 200ms ease-in-out;
162
+ }
163
+
164
+ .slide-up-enter,
165
+ .slide-up-leave-to {
166
+ transform: translateY(16px);
167
+ opacity: 0;
168
+ }
169
+
170
+ @keyframes elevate {
171
+ from {
172
+ transform: translateY(20px);
173
+ opacity: 0.5;
174
+ }
175
+
176
+ to {
177
+ transform: translateY(0);
178
+ opacity: 1;
179
+ }
180
+ }
181
+
182
+ @media (max-width: 640px) {
183
+ .bab-container {
184
+ height: 220px;
185
+ border-top-left-radius: 14px;
186
+ border-top-right-radius: 14px;
187
+ }
188
+
189
+ .bab-content {
190
+ grid-auto-columns: minmax(160px, 1fr);
191
+ }
192
+ }
193
+ </style>
@@ -0,0 +1,175 @@
1
+ <template>
2
+ <div class="color-picker" ref="root">
3
+ <div v-if="title" class="color-picker__title">{{ title }}</div>
4
+
5
+ <div class="color-picker__trigger" :style="{ backgroundColor: innerValue }" role="button" tabindex="0"
6
+ @click="toggle()" @keydown.enter.prevent="toggle()" @keydown.space.prevent="toggle()"
7
+ aria-haspopup="listbox" :aria-expanded="open ? 'true' : 'false'"></div>
8
+
9
+ <transition name="color-picker--fade">
10
+ <div v-show="open" class="color-picker__menu" role="listbox" :aria-label="title || 'Escolher cor'">
11
+ <div class="color-picker__grid">
12
+ <div v-for="c in colors" :key="c" class="color-picker__item" :style="{ backgroundColor: c }"
13
+ role="option" :aria-selected="c === innerValue ? 'true' : 'false'" tabindex="0"
14
+ @click="choose(c)" @keydown.enter.prevent="choose(c)" @keydown.space.prevent="choose(c)">
15
+ <span v-if="c === innerValue" class="color-picker__check"></span>
16
+ </div>
17
+ </div>
18
+ </div>
19
+ </transition>
20
+ </div>
21
+ </template>
22
+
23
+ <script>
24
+ export default {
25
+ name: "ColorPicker",
26
+ model: { prop: "value", event: "input" },
27
+ props: {
28
+ value: { type: String, default: "" },
29
+ defaultColor: { type: String, default: "#64B5F6" },
30
+ title: { type: String, default: "" },
31
+ colors: {
32
+ type: Array,
33
+ default: function () {
34
+ return [
35
+ "#E57373", "#F06292", "#FF6F61",
36
+ "#BA68C8", "#9575CD", "#8E24AA",
37
+ "#64B5F6", "#4FC3F7", "#3949AB",
38
+ "#039BE5", "#1976D2", "#1E88E5",
39
+ "#4DB6AC", "#81C784", "#AED581",
40
+ "#26A69A", "#2E7D32", "#0097A7",
41
+ "#7CB342", "#C0CA33", "#9CCC65",
42
+ "#FFD54F", "#FFB74D", "#FDD835",
43
+ "#FB8C00", "#FF8A65", "#F4511E",
44
+ "#A1887F", "#6D4C41", "#795548",
45
+ "#90A4AE", "#546E7A", "#9E9E9E",
46
+ "#000000", "#FFFFFF", "#B0BEC5"
47
+ ];
48
+ },
49
+ },
50
+ },
51
+ data() {
52
+ return { innerValue: "", open: false };
53
+ },
54
+ created() {
55
+ this.initialize();
56
+ },
57
+ watch: {
58
+ value(v) {
59
+ if (this.isEmpty(v)) {
60
+ const fallback = this.normalizeHex(this.defaultColor);
61
+ this.innerValue = fallback;
62
+ this.$emit("input", fallback);
63
+ } else if (v !== this.innerValue) {
64
+ this.innerValue = this.normalizeHex(v);
65
+ }
66
+ }
67
+ },
68
+ mounted() {
69
+ document.addEventListener("click", this.onClickOutside, { passive: true });
70
+ },
71
+ beforeDestroy() {
72
+ document.removeEventListener("click", this.onClickOutside);
73
+ },
74
+ methods: {
75
+ initialize() {
76
+ const start = this.isEmpty(this.value)
77
+ ? this.normalizeHex(this.defaultColor)
78
+ : this.normalizeHex(this.value);
79
+ this.innerValue = start;
80
+ if (this.isEmpty(this.value)) this.$emit("input", start);
81
+ },
82
+ isEmpty(v) {
83
+ return v === null || v === undefined || String(v).trim() === "";
84
+ },
85
+ normalizeHex(v) {
86
+ const s = String(v || "").trim().toUpperCase();
87
+ const hex = /^#([0-9A-F]{6}|[0-9A-F]{3})$/i;
88
+ return hex.test(s) ? s : "#64B5F6";
89
+ },
90
+ toggle() { this.open = !this.open; },
91
+ close() { this.open = false; },
92
+ choose(c) {
93
+ const val = this.normalizeHex(c);
94
+ this.innerValue = val;
95
+ this.$emit("input", val);
96
+ this.close();
97
+ },
98
+ onClickOutside(e) {
99
+ const root = this.$refs.root;
100
+ if (this.open && root && !root.contains(e.target)) this.close();
101
+ },
102
+ },
103
+ };
104
+ </script>
105
+
106
+ <style scoped>
107
+ .color-picker {
108
+ position: relative;
109
+ display: inline-grid;
110
+ gap: .375rem;
111
+ }
112
+
113
+ .color-picker__title {
114
+ font-weight: 500;
115
+ }
116
+
117
+ .color-picker__trigger {
118
+ width: 22px;
119
+ height: 22px;
120
+ border-radius: 50%;
121
+ cursor: pointer;
122
+ user-select: none;
123
+ }
124
+
125
+ .color-picker__menu {
126
+ position: absolute;
127
+ z-index: 40;
128
+ top: calc(100% + 8px);
129
+ left: 0;
130
+ min-width: 400px;
131
+ max-width: 90vw;
132
+ background: #fff;
133
+ border: 1px solid #e5e7eb;
134
+ border-radius: 10px;
135
+ box-shadow: 0 10px 24px rgba(0, 0, 0, .12);
136
+ padding: .5rem;
137
+ }
138
+
139
+ .color-picker__grid {
140
+ display: grid;
141
+ grid-template-columns: repeat(12, 28px);
142
+ gap: .5rem;
143
+ }
144
+
145
+ .color-picker__item {
146
+ width: 28px;
147
+ height: 28px;
148
+ border-radius: 50%;
149
+ cursor: pointer;
150
+ position: relative;
151
+ border: 1px solid rgba(0, 0, 0, .1);
152
+ }
153
+
154
+ .color-picker__check {
155
+ position: absolute;
156
+ width: 10px;
157
+ height: 10px;
158
+ background: white;
159
+ border-radius: 50%;
160
+ top: 50%;
161
+ left: 50%;
162
+ transform: translate(-50%, -50%);
163
+ }
164
+
165
+ .color-picker--fade-enter-active,
166
+ .color-picker--fade-leave-active {
167
+ transition: all .14s ease;
168
+ }
169
+
170
+ .color-picker--fade-enter,
171
+ .color-picker--fade-leave-to {
172
+ opacity: 0;
173
+ transform: translateY(-4px) scale(.98);
174
+ }
175
+ </style>
@@ -0,0 +1,161 @@
1
+ <template>
2
+ <div class="icon-picker" ref="root">
3
+ <div v-if="title" class="icon-picker__title">{{ title }}</div>
4
+
5
+ <div class="icon-picker__trigger" :style="{ backgroundColor: color }" role="button" tabindex="0"
6
+ @click="toggle()" @keydown.enter.prevent="toggle()" @keydown.space.prevent="toggle()"
7
+ aria-haspopup="listbox" :aria-expanded="open ? 'true' : 'false'">
8
+ <i :class="innerValue || 'far fa-square'"></i>
9
+ </div>
10
+
11
+ <transition name="icon-picker--fade">
12
+ <div v-show="open" class="icon-picker__menu" role="listbox" :aria-label="title || 'Escolher ícone'">
13
+ <div class="icon-picker__grid">
14
+ <div v-for="ic in icons" :key="ic" class="icon-picker__item"
15
+ :class="{ 'icon-picker__item--selected': ic === innerValue }" role="option"
16
+ :aria-selected="ic === innerValue ? 'true' : 'false'" tabindex="0" @click="choose(ic)"
17
+ @keydown.enter.prevent="choose(ic)" @keydown.space.prevent="choose(ic)"
18
+ :style="ic === innerValue ? { backgroundColor: color } : {}">
19
+ <i :class="ic"></i>
20
+ </div>
21
+ </div>
22
+ </div>
23
+ </transition>
24
+ </div>
25
+ </template>
26
+
27
+ <script>
28
+ export default {
29
+ name: "IconPicker",
30
+ model: { prop: "value", event: "input" },
31
+ props: {
32
+ value: { type: String, default: "fas fa-wallet" },
33
+ title: { type: String, default: "" },
34
+ color: { type: String, default: "#64B5F6" },
35
+ icons: {
36
+ type: Array,
37
+ default: function () {
38
+ return [
39
+ "fas fa-wallet", "fas fa-cash-register", "fas fa-shopping-cart", "fas fa-receipt", "fas fa-credit-card", "fas fa-money-bill", "fas fa-coins", "fas fa-percentage", "fas fa-dollar-sign", "fas fa-file-invoice", "fas fa-chart-line",
40
+ "fas fa-box-open", "fas fa-box", "fas fa-truck", "fas fa-warehouse", "fas fa-shipping-fast", "fas fa-dolly", "fas fa-pallet", "fas fa-trailer", "fas fa-clipboard-list",
41
+ "fas fa-building", "fas fa-city", "fas fa-home", "fas fa-briefcase", "fas fa-sitemap", "fas fa-industry", "fas fa-store", "fas fa-store-alt", "fas fa-landmark", "fas fa-hotel",
42
+ "fas fa-users", "fas fa-user-tie", "fas fa-user", "fas fa-user-friends", "fas fa-user-cog", "fas fa-user-shield", "fas fa-user-circle", "fas fa-id-badge", "fas fa-id-card",
43
+ "fas fa-cogs", "fas fa-tools", "fas fa-desktop", "fas fa-laptop", "fas fa-server", "fas fa-database", "fas fa-cloud", "fas fa-network-wired", "fas fa-microchip", "fas fa-robot",
44
+ "fas fa-shield-alt", "fas fa-lock", "fas fa-balance-scale", "fas fa-file-contract", "fas fa-gavel", "fas fa-passport", "fas fa-clipboard-check",
45
+ "fas fa-bullhorn", "fas fa-ad", "fas fa-mail-bulk", "fas fa-envelope", "fas fa-comments", "fas fa-headset", "fas fa-phone-alt", "fas fa-share-alt", "fas fa-at",
46
+ "fas fa-graduation-cap", "fas fa-book", "fas fa-book-open", "fas fa-chalkboard-teacher", "fas fa-lightbulb", "fas fa-brain", "fas fa-project-diagram",
47
+ "fas fa-car", "fas fa-gas-pump", "fas fa-utensils", "fas fa-coffee", "fas fa-gift", "fas fa-tag", "fas fa-leaf", "fas fa-seedling", "fas fa-heart", "fas fa-music", "fas fa-paw", "fas fa-plane"
48
+ ];
49
+ },
50
+ },
51
+ },
52
+ data() {
53
+ return {
54
+ innerValue: this.value || "",
55
+ open: false,
56
+ };
57
+ },
58
+ watch: {
59
+ value(v) { this.innerValue = v; }
60
+ },
61
+ mounted() {
62
+ document.addEventListener("click", this.onClickOutside, { passive: true });
63
+ },
64
+ beforeDestroy() {
65
+ document.removeEventListener("click", this.onClickOutside);
66
+ },
67
+ methods: {
68
+ toggle() { this.open = !this.open; },
69
+ close() { this.open = false; },
70
+ choose(ic) {
71
+ this.innerValue = ic;
72
+ this.$emit("input", ic);
73
+ this.close();
74
+ },
75
+ onClickOutside(e) {
76
+ const root = this.$refs.root;
77
+ if (this.open && root && !root.contains(e.target)) this.close();
78
+ }
79
+ }
80
+ };
81
+ </script>
82
+
83
+ <style scoped>
84
+ .icon-picker {
85
+ position: relative;
86
+ display: inline-grid;
87
+ gap: .375rem;
88
+ margin-bottom: 10px;
89
+ }
90
+
91
+ .icon-picker__title {
92
+ font-weight: 500;
93
+ }
94
+
95
+ .icon-picker__trigger {
96
+ width: 56px;
97
+ height: 56px;
98
+ border-radius: 50%;
99
+ display: grid;
100
+ place-items: center;
101
+ cursor: pointer;
102
+ user-select: none;
103
+ }
104
+
105
+ .icon-picker__trigger i {
106
+ font-size: 22px;
107
+ color: #fff;
108
+ }
109
+
110
+ .icon-picker__menu {
111
+ position: absolute;
112
+ z-index: 40;
113
+ top: calc(100% + 8px);
114
+ left: 0;
115
+ width: 540px;
116
+ height: 320px;
117
+ overflow-y: auto;
118
+ background: #fff;
119
+ border: 1px solid #e5e7eb;
120
+ border-radius: 10px;
121
+ box-shadow: 0 10px 24px rgba(0, 0, 0, .12);
122
+ padding: .5rem .5rem .6rem;
123
+ }
124
+
125
+ .icon-picker__grid {
126
+ display: grid;
127
+ grid-template-columns: repeat(12, 36px);
128
+ gap: .5rem;
129
+ justify-content: start;
130
+ }
131
+
132
+ .icon-picker__item {
133
+ width: 36px;
134
+ height: 36px;
135
+ border-radius: 50%;
136
+ background: #f3f4f6;
137
+ display: grid;
138
+ place-items: center;
139
+ cursor: pointer;
140
+ }
141
+
142
+ .icon-picker__item i {
143
+ font-size: 14px;
144
+ color: #6b7280;
145
+ }
146
+
147
+ .icon-picker__item--selected i {
148
+ color: #fff;
149
+ }
150
+
151
+ .icon-picker--fade-enter-active,
152
+ .icon-picker--fade-leave-active {
153
+ transition: all .14s ease;
154
+ }
155
+
156
+ .icon-picker--fade-enter,
157
+ .icon-picker--fade-leave-to {
158
+ opacity: 0;
159
+ transform: translateY(-4px) scale(.98);
160
+ }
161
+ </style>