@nightshadeui/core 2.0.0-dev.3

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.
@@ -0,0 +1,278 @@
1
+ <template>
2
+ <component
3
+ :is="tagName"
4
+ class="InputBase"
5
+ :class="[
6
+ `InputBase-${effectiveStyle.kind}`,
7
+ `input-kind-${effectiveStyle.kind}`,
8
+ `InputBase-${effectiveStyle.size}`,
9
+ {
10
+ 'InputBase-fixed-height': fixedHeight,
11
+ 'InputBase-shadow': effectiveStyle.shadow,
12
+ 'InputBase-round': effectiveStyle.round,
13
+ 'InputBase-disabled': disabled,
14
+ 'InputBase-force-focus': forceFocus,
15
+ 'InputBase-force-hover': forceHover,
16
+ }
17
+ ]"
18
+ @mouseenter="hover = true"
19
+ @mouseleave="hover = false"
20
+ @focusin="focus = true"
21
+ @focusout="focus = false">
22
+ <template v-if="label">
23
+ <Tab
24
+ v-if="labelStyle === 'tab'"
25
+ class="Label TabLabel"
26
+ :label="label" />
27
+ <div
28
+ v-if="labelStyle === 'text'"
29
+ class="Label TextLabel">
30
+ {{ label }}
31
+ </div>
32
+ <div
33
+ v-if="labelStyle === 'inline'"
34
+ class="Label InlineLabel">
35
+ {{ label }}
36
+ </div>
37
+ </template>
38
+ <div class="InputElement Container">
39
+ <slot />
40
+ </div>
41
+ </component>
42
+ </template>
43
+
44
+ <script>
45
+ import Tab from './Tab.vue';
46
+
47
+ export default {
48
+
49
+ components: {
50
+ Tab,
51
+ },
52
+
53
+ props: {
54
+ tagName: { default: 'label' },
55
+ kind: { type: String, default: 'base' },
56
+ label: { type: String },
57
+ labelStyle: { type: String, default: 'inline' },
58
+
59
+ size: { type: String, default: 'normal' },
60
+ fixedHeight: { type: Boolean, default: true },
61
+ shadow: { type: Boolean, default: false },
62
+ round: { type: Boolean, default: false },
63
+
64
+ disabled: { type: Boolean, default: false },
65
+ forceFocus: { type: Boolean, default: false },
66
+ forceHover: { type: Boolean, default: false },
67
+
68
+ hoverOverrides: { type: Object },
69
+ focusOverrides: { type: Object },
70
+ },
71
+
72
+ data() {
73
+ return {
74
+ hover: false,
75
+ focus: false,
76
+ };
77
+ },
78
+
79
+ computed: {
80
+
81
+ baseStyle() {
82
+ return {
83
+ kind: this.kind,
84
+ size: this.size,
85
+ shadow: this.shadow,
86
+ round: this.round,
87
+ labelStyle: this.labelStyle,
88
+ };
89
+ },
90
+
91
+ effectiveStyle() {
92
+ const {
93
+ baseStyle,
94
+ hoverOverrides = {},
95
+ focusOverrides = {},
96
+ hover,
97
+ focus,
98
+ forceHover,
99
+ forceFocus
100
+ } = this;
101
+ const style = Object.assign({}, baseStyle);
102
+ if (hover || forceHover) {
103
+ Object.assign(style, hoverOverrides);
104
+ }
105
+ if (focus || forceFocus) {
106
+ Object.assign(style, focusOverrides);
107
+ }
108
+ return style;
109
+ },
110
+ }
111
+
112
+ };
113
+ </script>
114
+
115
+ <style scoped>
116
+ .InputBase {
117
+ --InputBase-size: var(--input-size);
118
+ --InputBase-padding: var(--sp1-5);
119
+ --InputBase-gap: var(--sp);
120
+ --InputBase-font-size: var(--font-size);
121
+
122
+ --InputBase-text-color: var(--text-color);
123
+ --InputBase-surface: var(--base-color);
124
+
125
+ --InputBase-outline-color: transparent;
126
+ --InputBase-outline-size: var(--input-outline-size);
127
+ --InputBase-outline-offset: var(--input-outline-offset);
128
+
129
+ --InputBase-border-size: var(--input-border-size);
130
+ --InputBase-border-color: var(--input-border-color);
131
+ --InputBase-border-radius: var(--border-radius);
132
+
133
+ --InputBase-label-color: var(--input-label-color);
134
+ --InputBase-label-font-size: var(--font-size-s);
135
+ --InputBase-tab-surface-color: var(--input-surface-color-top);
136
+ --InputBase-tab-text-color: var(--input-surface-color-text);
137
+
138
+ position: relative;
139
+ display: flex;
140
+ flex-flow: column nowrap;
141
+ outline: 0;
142
+ z-index: var(--input-z);
143
+ }
144
+
145
+ .Container {
146
+ position: relative;
147
+ z-index: 1;
148
+
149
+ display: flex;
150
+ align-items: center;
151
+ box-sizing: border-box;
152
+ padding: var(--InputBase-padding);
153
+ gap: var(--InputBase-gap);
154
+ width: 100%;
155
+ min-height: var(--InputBase-size);
156
+
157
+ color: var(--InputBase-text-color);
158
+ background: var(--InputBase-surface);
159
+
160
+ outline: var(--InputBase-outline-size) solid var(--InputBase-outline-color);
161
+ outline-offset: var(--InputBase-outline-offset);
162
+
163
+ border: var(--InputBase-border-size) solid var(--InputBase-border-color);
164
+ border-radius: var(--InputBase-border-radius);
165
+
166
+ font-size: var(--InputBase-font-size);
167
+
168
+ transition: color .3s, outline .3s, border-radius .3s, filter .3s;
169
+ }
170
+
171
+ /* States */
172
+
173
+ .InputBase:not(.InputBase-disabled):focus-within, .InputBase.InputBase-force-focus {
174
+ z-index: 10;
175
+ --InputBase-outline-color: var(--input-focus-light-color);
176
+ --InputBase-border-color: var(--input-focus-medium-color);
177
+ --InputBase-label-color: var(--input-focus-medium-color);
178
+ /* --InputBase-tab-surface-color: var(--input-focus-light-color); */
179
+ /* --InputBase-tab-text-color: var(--input-surface-text-color); */
180
+ }
181
+
182
+ .InputBase-disabled .Container {
183
+ --InputBase-surface: var(--color-base-100);
184
+ opacity: .6;
185
+ filter: saturate(40%);
186
+ cursor: not-allowed;
187
+ }
188
+
189
+ /* Styles */
190
+
191
+ .InputBase-fixed-height .Container {
192
+ height: var(--InputBase-size);
193
+ }
194
+
195
+ .InputBase-round {
196
+ --InputBase-border-radius: var(--border-radius-m);
197
+ }
198
+
199
+ .InputBase-shadow .Container {
200
+ box-shadow:
201
+ 0 0 5px -1px var(--shadow-color-light) inset;
202
+ }
203
+
204
+ /* Sizes */
205
+
206
+ .InputBase-small {
207
+ --InputBase-size: var(--input-size-s);
208
+ --InputBase-font-size: var(--font-size-s);
209
+ --InputBase-label-font-size: var(--font-size-xs);
210
+ --InputBase-padding: var(--sp);
211
+ }
212
+
213
+ .InputBase-large {
214
+ --InputBase-size: var(--input-size-l);
215
+ --InputBase-font-size: var(--font-size-l);
216
+ --InputBase-padding: var(--sp2);
217
+ }
218
+
219
+ /* Labels */
220
+
221
+ .Label {
222
+ font-size: var(--InputBase-label-font-size);
223
+ max-width: calc(100% - 2 * var(--sp2));
224
+ }
225
+
226
+ .Label.TabLabel {
227
+ align-self: flex-start;
228
+ position: relative;
229
+ z-index: 0;
230
+ margin: 0 var(--sp2);
231
+
232
+ --Tab-surface: var(--InputBase-tab-surface-color);
233
+ --Tab-color: var(--InputBase-tab-text-color);
234
+ --Tab-size: calc(.75 * var(--InputBase-size));
235
+ --Tab-cap-size: calc(.75 * var(--InputBase-size));
236
+ }
237
+
238
+ .Label.TextLabel {
239
+ margin: 0 var(--InputBase-padding);
240
+ line-height: 1.5;
241
+ height: var(--sp3);
242
+
243
+ color: var(--InputBase-text-color);
244
+
245
+ white-space: nowrap;
246
+ overflow: hidden;
247
+ text-overflow: ellipsis;
248
+ }
249
+
250
+ .Label.InlineLabel {
251
+ position: absolute;
252
+ top: 0;
253
+ left: 0;
254
+ transform: translateY(-50%);
255
+ z-index: 2;
256
+ margin: 0 var(--InputBase-padding);
257
+ padding: 0 2px;
258
+
259
+ color: var(--InputBase-label-color);
260
+
261
+ white-space: nowrap;
262
+ overflow: hidden;
263
+ text-overflow: ellipsis;
264
+ }
265
+
266
+ .Label.InlineLabel::after {
267
+ content: '';
268
+ position: absolute;
269
+ left: 0;
270
+ right: 0;
271
+ top: 50%;
272
+ height: var(--sp);
273
+ transform: translateY(-50%);
274
+ z-index: -1;
275
+
276
+ background: var(--InputBase-surface);
277
+ }
278
+ </style>
@@ -0,0 +1,36 @@
1
+ <template>
2
+ <component
3
+ :is="tagName"
4
+ class="InputGroup">
5
+ <slot />
6
+ </component>
7
+ </template>
8
+
9
+ <script>
10
+ export default {
11
+
12
+ props: {
13
+ tagName: { type: String, default: 'div' },
14
+ }
15
+
16
+ };
17
+ </script>
18
+
19
+ <style scoped>
20
+ .InputGroup {
21
+ display: flex;
22
+ flex-flow: row nowrap;
23
+ align-items: end;
24
+ }
25
+
26
+ .InputGroup:deep(> .InputElement:not(:first-child)) {
27
+ border-start-start-radius: 0;
28
+ border-end-start-radius: 0;
29
+ margin-left: calc(-1 * var(--input-border-size));
30
+ }
31
+
32
+ .InputGroup:deep(> .InputElement:not(:last-child)) {
33
+ border-start-end-radius: 0;
34
+ border-end-end-radius: 0;
35
+ }
36
+ </style>
@@ -0,0 +1,154 @@
1
+ <template>
2
+ <InputBase
3
+ class="InputSelect"
4
+ v-bind="{
5
+ ...$props
6
+ }"
7
+ tabindex="0"
8
+ @click="show()">
9
+ <slot name="before" />
10
+ <i
11
+ v-if="itemIcon"
12
+ class="Icon"
13
+ :class="itemIcon" />
14
+ <span
15
+ v-if="selectedItem"
16
+ class="Value">
17
+ {{ itemTitle }}
18
+ </span>
19
+ <span
20
+ v-if="!selectedItem"
21
+ class="Placeholder">
22
+ {{ placeholder }}
23
+ </span>
24
+ <div
25
+ ref="icon"
26
+ class="DropdownIcon">
27
+ <slot name="iconDropdown">
28
+ <i :class="iconDropdown" />
29
+ </slot>
30
+ </div>
31
+ <ContextMenu
32
+ v-if="!disabled && menuShown"
33
+ anchorRef="icon"
34
+ :items="getMenuItems()"
35
+ :search="search"
36
+ :overlayShown="false"
37
+ @hide="menuShown = false" />
38
+ <slot name="after" />
39
+ </InputBase>
40
+ </template>
41
+
42
+ <script>
43
+ import InputBase from './InputBase.vue';
44
+
45
+ export default {
46
+
47
+ props: {
48
+ ...InputBase.props,
49
+ modelValue: {},
50
+ items: { type: Array, default: () => [] },
51
+ placeholder: { type: String },
52
+ readonly: { type: Boolean },
53
+ search: { type: Boolean, default: false },
54
+ },
55
+
56
+ emits: [
57
+ 'focus',
58
+ 'blur',
59
+ 'update:modelValue'
60
+ ],
61
+
62
+ data() {
63
+ return {
64
+ menuShown: false,
65
+ };
66
+ },
67
+
68
+ computed: {
69
+
70
+ selectedItem() {
71
+ return this.items.find(item => (item.value ?? item) === this.modelValue);
72
+ },
73
+
74
+ itemTitle() {
75
+ const { selectedItem } = this;
76
+ return selectedItem?.title ?? selectedItem;
77
+ },
78
+
79
+ itemIcon() {
80
+ const { selectedItem } = this;
81
+ return selectedItem?.icon;
82
+ },
83
+
84
+ iconDropdown() {
85
+ return this.$nightshadeIcons?.dropdown ?? 'fas fa-angle-down';
86
+ },
87
+
88
+ },
89
+
90
+ methods: {
91
+
92
+ onInput(ev) {
93
+ this.$emit('update:modelValue', ev.target.value);
94
+ },
95
+
96
+ selectValue(value) {
97
+ this.$emit('update:modelValue', value);
98
+ this.menuShown = false;
99
+ },
100
+
101
+ getMenuItems() {
102
+ return this.items.map(item => {
103
+ const title = typeof item === 'string' ? item : item.title;
104
+ const value = typeof item === 'string' ? item : item.value;
105
+ return {
106
+ title,
107
+ checked: value === this.modelValue,
108
+ disabled: item.disabled,
109
+ description: item.description,
110
+ icon: item.icon ?? undefined,
111
+ activate: () => {
112
+ if (this.disabled) {
113
+ return;
114
+ }
115
+ this.selectValue(value);
116
+ }
117
+ };
118
+ });
119
+ },
120
+
121
+ show() {
122
+ if (!this.disabled) {
123
+ this.menuShown = true;
124
+ }
125
+ }
126
+
127
+ }
128
+
129
+ };
130
+ </script>
131
+
132
+ <style scoped>
133
+ .Icon {
134
+ opacity: 0.5;
135
+ width: var(--sp2);
136
+ display: flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ padding-right: var(--sphalf);
140
+ }
141
+
142
+ .Value, .Placeholder {
143
+ line-height: var(--input-size);
144
+ flex: 1 1 auto;
145
+ white-space: nowrap;
146
+ text-overflow: ellipsis;
147
+ overflow: hidden;
148
+ }
149
+
150
+ .DropdownIcon {
151
+ color: var(--color-text-2);
152
+ font-size: var(--font-size-small);
153
+ }
154
+ </style>
@@ -0,0 +1,87 @@
1
+ <template>
2
+ <InputBase
3
+ class="InputText"
4
+ v-bind="{
5
+ ...$props
6
+ }">
7
+ <slot name="before" />
8
+ <input
9
+ ref="input"
10
+ :value="modelValue"
11
+ :type="type"
12
+ :placeholder="placeholder"
13
+ :readonly="readonly"
14
+ :disabled="disabled"
15
+ :min="min"
16
+ :max="max"
17
+ :step="step"
18
+ autocomplete="off"
19
+ @input="onInput($event)"
20
+ @focus="$emit('focus', $event)"
21
+ @blur="$emit('blur', $event)" />
22
+ <slot name="after" />
23
+ </InputBase>
24
+ </template>
25
+
26
+ <script>
27
+ import InputBase from './InputBase.vue';
28
+
29
+ export default {
30
+
31
+ props: {
32
+ ...InputBase.props,
33
+ modelValue: { type: [String, Number] },
34
+ type: { type: String },
35
+ placeholder: { type: String },
36
+ min: { type: Number },
37
+ max: { type: Number },
38
+ step: { type: Number },
39
+ autoFocus: { type: Boolean },
40
+ readonly: { type: Boolean },
41
+ },
42
+
43
+ emits: [
44
+ 'focus',
45
+ 'blur',
46
+ 'input',
47
+ 'update:modelValue'
48
+ ],
49
+
50
+ mounted() {
51
+ if (this.autoFocus) {
52
+ this.$refs.input?.focus();
53
+ }
54
+ },
55
+
56
+ methods: {
57
+
58
+ onInput(ev) {
59
+ this.$emit('update:modelValue', ev.target.value);
60
+ }
61
+
62
+ }
63
+
64
+ };
65
+ </script>
66
+
67
+ <style scoped>
68
+ .InputText {
69
+ --InputBase-padding: var(--sp1-5);
70
+ }
71
+
72
+ input, textarea {
73
+ -webkit-appearance: none;
74
+ box-sizing: border-box;
75
+ flex: 1;
76
+ padding: 0;
77
+ border: 0;
78
+ width: 100%;
79
+ min-width: 0;
80
+ outline: 0;
81
+ user-select: text;
82
+ background: transparent;
83
+ color: inherit;
84
+ font: inherit;
85
+ cursor: inherit;
86
+ }
87
+ </style>
@@ -0,0 +1,114 @@
1
+ <template>
2
+ <InputBase
3
+ class="InputTextarea"
4
+ v-bind="{
5
+ ...$props
6
+ }"
7
+ :fixedHeight="false">
8
+ <slot name="before" />
9
+ <textarea
10
+ ref="input"
11
+ :value="modelValue"
12
+ :placeholder="placeholder"
13
+ :readonly="readonly"
14
+ :disabled="disabled"
15
+ :rows="rows"
16
+ resize="none"
17
+ autocomplete="off"
18
+ @input="onInput($event)"
19
+ @focus="$emit('focus', $event)"
20
+ @blur="$emit('blur', $event)" />
21
+ <slot name="after" />
22
+ </InputBase>
23
+ </template>
24
+
25
+ <script>
26
+ import InputBase from './InputBase.vue';
27
+
28
+ export default {
29
+
30
+ props: {
31
+ ...InputBase.props,
32
+ modelValue: { type: String },
33
+ placeholder: { type: String },
34
+ rows: { type: Number },
35
+ autoSize: { type: Boolean, default: true },
36
+ autoFocus: { type: Boolean },
37
+ readonly: { type: Boolean },
38
+ },
39
+
40
+ emits: [
41
+ 'focus',
42
+ 'blur',
43
+ 'input',
44
+ 'update:modelValue'
45
+ ],
46
+
47
+ watch: {
48
+
49
+ modelValue: {
50
+ handler() {
51
+ const textarea = this.$refs.input;
52
+ if (this.autoSize && textarea) {
53
+ textarea.style.height = 'auto';
54
+ textarea.style.height = textarea.scrollHeight + 'px';
55
+ }
56
+ },
57
+ },
58
+
59
+ },
60
+
61
+ mounted() {
62
+ this.$nextTick(() => {
63
+ const textarea = this.$refs.input;
64
+ if (this.autoFocus) {
65
+ textarea.focus();
66
+ }
67
+ if (this.autoSize) {
68
+ textarea.style.height = textarea.scrollHeight + 'px';
69
+ textarea.style.overflowY = 'hidden';
70
+ }
71
+ });
72
+ },
73
+
74
+ methods: {
75
+
76
+ onInput(ev) {
77
+ this.$emit('update:modelValue', ev.target.value);
78
+ },
79
+
80
+ }
81
+
82
+ };
83
+ </script>
84
+
85
+ <style scoped>
86
+ .InputTextarea {
87
+ --InputBase-padding: var(--sp1) var(--sp1-5);
88
+ }
89
+
90
+ input, textarea {
91
+ -webkit-appearance: none;
92
+ box-sizing: border-box;
93
+ flex: 1;
94
+ padding: 0;
95
+ border: 0;
96
+ width: 100%;
97
+ min-width: 0;
98
+ outline: 0;
99
+ user-select: text;
100
+ background: transparent;
101
+ color: inherit;
102
+ font: inherit;
103
+ cursor: inherit;
104
+ resize: none;
105
+ }
106
+
107
+ .InputBase:deep(.Container) {
108
+ overflow-y: auto;
109
+ }
110
+
111
+ textarea {
112
+ align-self: flex-start;
113
+ }
114
+ </style>
package/src/Sizer.vue ADDED
@@ -0,0 +1,16 @@
1
+ <template>
2
+ <div class="Sizer" />
3
+ </template>
4
+
5
+ <script>
6
+ export default {
7
+ };
8
+ </script>
9
+
10
+ <style scoped>
11
+ .Sizer {
12
+ flex: 1;
13
+ min-width: 0;
14
+ min-height: 0;
15
+ }
16
+ </style>