fu-kit 0.0.1-beta.2 → 0.0.1-beta.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.
Files changed (43) hide show
  1. package/README.md +47 -19
  2. package/dist/favicon.ico +0 -0
  3. package/dist/img/splash-screen.8bd73950.jpg +0 -0
  4. package/dist/index.html +1 -0
  5. package/dist/js/app.53980592.js +2 -0
  6. package/dist/js/app.53980592.js.map +1 -0
  7. package/dist/js/chunk-vendors.fe8aa7a9.js +8 -0
  8. package/dist/js/chunk-vendors.fe8aa7a9.js.map +1 -0
  9. package/package.json +36 -36
  10. package/reset.scss +60 -0
  11. package/root.scss +130 -0
  12. package/scss.scss +5 -3
  13. package/src/App.vue +122 -30
  14. package/src/Home.vue +112 -0
  15. package/src/assets/splash-screen.jpg +0 -0
  16. package/src/components/FuButton.vue +104 -27
  17. package/src/components/FuButtonLink.vue +79 -0
  18. package/src/components/FuCodeView.vue +58 -0
  19. package/src/components/FuCopy.vue +103 -0
  20. package/src/components/FuProgressRadial.vue +117 -0
  21. package/src/components/FuSelect.vue +84 -55
  22. package/src/components/FuSelectX.vue +317 -0
  23. package/src/components/FuSidebar.vue +90 -0
  24. package/src/components/FuText.vue +98 -60
  25. package/src/components/FuTextarea.vue +118 -0
  26. package/src/docs/DocButton.vue +67 -0
  27. package/src/docs/DocSandbox.vue +71 -0
  28. package/src/docs/DocSelect.vue +55 -0
  29. package/src/docs/DocSidebar.vue +93 -0
  30. package/src/docs/DocText.vue +59 -0
  31. package/src/docs/DocTextarea.vue +64 -0
  32. package/src/docs/DocTypo.vue +98 -0
  33. package/src/main.js +9 -5
  34. package/src/router.js +29 -17
  35. package/src/scss/colors.scss +16 -6
  36. package/src/scss/typo.scss +5 -14
  37. package/src/scss/ui.scss +22 -38
  38. package/src/scss/utils.scss +36 -0
  39. package/src/styles.scss +15 -0
  40. package/src/utils/media.js +1 -0
  41. package/src/utils/woosh.js +2 -0
  42. package/vue.config.js +1 -4
  43. package/src/views/Home.vue +0 -24
@@ -0,0 +1,317 @@
1
+ <template>
2
+ <label class="fu-select-x" v-bind="$attrs">
3
+ <input
4
+ tabindex="0"
5
+ class="fu-select-x_input"
6
+ ref="refSearch"
7
+ v-model="search"
8
+ :placeholder="model"
9
+ @keydown="onTextKeydown"
10
+ @focus="onTextFocus"
11
+ @blur="onSomeBlur"
12
+ spellcheck="false"
13
+ />
14
+ <span class="fu-select-x_list" v-show="filteredItems.length" ref="refList" @keydown="onArrows">
15
+ <button
16
+ class="fu-select-x_list-item"
17
+ :class="{'_selected': e.value === model}"
18
+ tabindex="-1"
19
+ v-for="(e) in filteredItems"
20
+ @click="onSelect($event,e)"
21
+ @blur="onSomeBlur"
22
+ >
23
+ {{ e.label }}
24
+ </button>
25
+ </span>
26
+ </label>
27
+ </template>
28
+
29
+ <script>
30
+ import { computed, nextTick, ref, watch } from 'vue'
31
+
32
+ import UiText from './FuText.vue'
33
+ import FuButton from './FuButton.vue'
34
+
35
+ export default {
36
+ name: 'fu-select-x',
37
+ components: { FuButton, UiText },
38
+ props: {
39
+ modelValue: { type: [ String, Number ], default: '' },
40
+ options: { type: Array, default: [] },
41
+ allowCustom: { type: Boolean, default: false },
42
+ },
43
+ emits: [ 'update:modelValue', 'select' ],
44
+ setup (props, { emit }) {
45
+ const refSearch = ref(null)
46
+ const refList = ref(null)
47
+ const search = ref('')
48
+ const model = ref(props.modelValue)
49
+
50
+ watch(() => props.modelValue, (value) => {
51
+ model.value = value
52
+ search.value = value
53
+ })
54
+
55
+ const normalItems = computed(() => {
56
+ return props.options.map(o => {
57
+ if (typeof o === 'object' && o !== null && !Array.isArray(o)) {
58
+ return o
59
+ } else {
60
+ return { value: o, label: o }
61
+ }
62
+ })
63
+ })
64
+
65
+ const filteredItems = computed(() => {
66
+ if (!search.value || search.value === model.value) return normalItems.value
67
+ return normalItems.value.filter((i) => String(i.label).includes(search.value))
68
+ })
69
+
70
+ const selectedItem = computed(() => normalItems.value.find(o => model.value === o.value))
71
+
72
+ const onSelect = (event, item) => {
73
+ search.value = item.value
74
+ model.value = item.value
75
+ emit('update:modelValue', model.value)
76
+ emit('select', model.value)
77
+
78
+ event.target.blur()
79
+ }
80
+
81
+ const onTextFocus = async (e) => {
82
+ search.value = model.value
83
+ await nextTick()
84
+ e.target.setSelectionRange(0, -1)
85
+ }
86
+
87
+ const onTextKeydown = (e) => {
88
+ if (![ 'ArrowDown', 'ArrowUp', 'Enter', 'Escape' ].includes(e.key)) return
89
+
90
+ if (e.key === 'Escape') return document.activeElement.blur()
91
+
92
+ const nodes = Array.prototype.slice.call(refList.value.children)
93
+
94
+ if (e.key === 'Enter') {
95
+ if (nodes[0]) {
96
+ nodes[0].click()
97
+ } else if (props.allowCustom) {
98
+ onSelect(e, { label: search.value, value: search.value })
99
+ }
100
+
101
+ e.preventDefault()
102
+ e.target.blur()
103
+
104
+ return
105
+ }
106
+
107
+ if (!nodes.length) return
108
+
109
+ switch (e.key) {
110
+ case 'ArrowDown':
111
+ e.preventDefault()
112
+ nodes[0].focus()
113
+ break
114
+ case 'ArrowUp':
115
+ e.preventDefault()
116
+ nodes[nodes.length - 1].focus()
117
+ break
118
+ }
119
+
120
+ e.preventDefault()
121
+ }
122
+
123
+ const onArrows = (e) => {
124
+ if (![ 'ArrowDown', 'ArrowUp', 'Escape' ].includes(e.key)) return
125
+
126
+ if (e.key === 'Escape') return document.activeElement.blur()
127
+
128
+ if (refList.value !== document.activeElement.parentElement) return
129
+
130
+ switch (e.key) {
131
+ case 'ArrowDown':
132
+ e.preventDefault()
133
+ focusJump(+1)
134
+ break
135
+ case 'ArrowUp':
136
+ e.preventDefault()
137
+ focusJump(-1)
138
+ break
139
+ }
140
+ }
141
+
142
+ const focusJump = (next = -1) => {
143
+ const foElm = document.activeElement.parentElement
144
+ if (foElm !== refList.value && foElm === refSearch.value) return
145
+
146
+ const nodes = Array.prototype.slice.call(refList.value.children)
147
+ const liRef = document.activeElement
148
+ const fi = nodes.indexOf(liRef)
149
+
150
+
151
+ if (next > 0) {
152
+ if (fi === nodes.length - 1) return nodes[0].focus()
153
+ if (nodes[fi + 1]) nodes[fi + 1].focus()
154
+ } else if (next < 0) {
155
+ if (fi === 0) return nodes[nodes.length - 1].focus()
156
+ if (nodes[fi - 1]) nodes[fi - 1].focus()
157
+ }
158
+ }
159
+
160
+ const onSomeBlur = async (e) => {
161
+ await nextTick() // todo: investigate why it's required
162
+ if (
163
+ refSearch.value !== document.activeElement &&
164
+ refList.value !== document.activeElement.parentElement
165
+ ) {
166
+ search.value = model.value
167
+ }
168
+ }
169
+
170
+ return {
171
+ refSearch,
172
+ refList,
173
+ search,
174
+ model,
175
+ filteredItems,
176
+ selectedItem,
177
+ onTextKeydown,
178
+ onArrows,
179
+ onSelect,
180
+ onTextFocus,
181
+ onSomeBlur,
182
+ }
183
+ },
184
+ }
185
+ </script>
186
+
187
+ <style lang="scss">
188
+ :root {
189
+ --select-x-bg: black;
190
+ --select-x-border-color: #{pal(primary)};
191
+ --select-x-border-width: 1px;
192
+ }
193
+ </style>
194
+
195
+ <style lang="scss" scoped>
196
+ .fu-select-x {
197
+ @include typo(200);
198
+
199
+ padding: 0;
200
+ display: flex;
201
+ box-sizing: border-box;
202
+ align-items: center;
203
+ justify-content: stretch;
204
+ border-style: var(--ui-lt-border-style);
205
+ border-width: var(--ui-lt-border-width);
206
+ border-color: var(--ui-pal-lateral);
207
+ border-radius: var(--ui-lt-border-radius);
208
+ transition-duration: 240ms;
209
+ transition-timing-function: ease-in-out;
210
+ transition-property: border-color, box-shadow;
211
+ height: var(--ui-lt-h);
212
+ position: relative;
213
+ background: var(--ui-pal-bg);
214
+
215
+ &_input {
216
+ @include typo(200);
217
+ @include spacing-padding(100, 300);
218
+
219
+ color: var(--ui-pal-text);
220
+ caret-color: var(--ui-pal);
221
+ min-height: min(100%);
222
+ border: none;
223
+ outline: none;
224
+ background: transparent;
225
+ box-sizing: border-box;
226
+ flex: 1;
227
+ display: block;
228
+ min-width: 0;
229
+ margin: 0;
230
+
231
+ &:focus {
232
+ outline: none;
233
+ }
234
+
235
+ &:not(:focus)::placeholder {
236
+ color: pal(prime);
237
+ }
238
+
239
+ &::selection {
240
+ background-color: var(--ui-pal);
241
+ color: var(--ui-pal-text-select);
242
+ }
243
+ }
244
+
245
+ &_list {
246
+ @include scrollbar-awesome();
247
+
248
+ overflow: auto;
249
+ max-height: 30vh;
250
+ max-width: 100vw;
251
+ display: none;
252
+ flex-direction: column;
253
+ justify-content: stretch;
254
+ border-width: var(--ui-lt-border-width);
255
+ border-radius: var(--ui-lt-border-radius);
256
+ border-color: var(--ui-pal);
257
+ border-style: solid;
258
+ position: absolute;
259
+ left: calc(var(var(--ui-lt-border-width)-1));
260
+ top: 100%;
261
+ min-width: calc(100% + var(--ui-lt-border-width));
262
+ background: var(--ui-pal-bg);
263
+ margin-top: spacing(200);
264
+ z-index: var(--lt-z-pop);
265
+
266
+ &-item {
267
+ @include spacing-padding(100, 300);
268
+ @include typo(200, 300);
269
+
270
+ border: 0 none;
271
+ text-decoration: none;
272
+ border-radius: 0;
273
+ justify-content: left;
274
+ text-align: left;
275
+ font-weight: inherit;
276
+ background: transparent;
277
+ outline: none;
278
+
279
+ &:hover {
280
+ background-color: var(--ui-pal-lateral);
281
+ color: var(--ui-pal-text);
282
+ }
283
+
284
+ &._selected {
285
+ color: var(--ui-pal-acc);
286
+ background-color: var(--ui-pal);
287
+ }
288
+
289
+ &:focus {
290
+ color: var(--ui-pal-acc);
291
+ background-color: var(--ui-pal);
292
+ }
293
+ }
294
+ }
295
+
296
+ &:focus-within &_list {
297
+ display: flex;
298
+ }
299
+
300
+ &:hover {
301
+ outline: none;
302
+ box-shadow: 0 5px 12px -4px rgb(var(--rgb-dark), 0.2);
303
+ }
304
+
305
+ &:focus-within {
306
+ outline: none;
307
+ box-shadow: 0 0 0 0 var(--ui-pal);
308
+ border-color: var(--ui-pal);
309
+ }
310
+
311
+ &._disabled {
312
+ border: var(--ui-lt-border-width) var(--ui-lt-disabled-border-style) var(--ui-pal-disabled-border);
313
+ background: transparent;
314
+ box-shadow: none;
315
+ }
316
+ }
317
+ </style>
@@ -0,0 +1,90 @@
1
+ <template>
2
+ <div class="fu-sidebar" :class="{'_shown':isOpen}" @click.self="$emit('close')">
3
+ <transition name="bounce">
4
+ <div class="fu-sidebar_content" v-if="isOpen">
5
+ <slot />
6
+ </div>
7
+ </transition>
8
+ </div>
9
+ </template>
10
+
11
+ <script>
12
+ import FuButton from './FuButton.vue'
13
+
14
+ export default {
15
+ name: 'FuSidebar',
16
+ emits: [ 'close' ],
17
+ components: { FuButton },
18
+ props: {
19
+ isOpen: { type: Boolean, default: false },
20
+ side: { type: String, default: 'left' },
21
+ },
22
+ }
23
+ </script>
24
+
25
+ <style lang="scss">
26
+ :root {
27
+ --ui-sidebar-max-w: 45vw;
28
+ --ui-sidebar-min-w: 25vw;
29
+
30
+ @include respond-below(xs) {
31
+ --ui-sidebar-max-w: 100vw;
32
+ --ui-sidebar-min-w: 90vw;
33
+ }
34
+ }
35
+ </style>
36
+
37
+ <style scoped lang="scss">
38
+ .fu-sidebar {
39
+ position: fixed;
40
+ left: 0;
41
+ top: 0;
42
+ right: 0;
43
+ bottom: 0;
44
+ z-index: var(--lt-z-nav);
45
+ background-color: transparent;
46
+ transition-timing-function: linear;
47
+ transition-duration: 600ms;
48
+ transition-property: background-color, visibility;
49
+ pointer-events: none;
50
+ visibility: hidden;
51
+
52
+ &._shown {
53
+ background: var(--pal-overlay);
54
+ visibility: visible;
55
+ pointer-events: unset;
56
+ }
57
+
58
+ &_content {
59
+ @include scrollbar-awesome();
60
+
61
+ overflow: auto;
62
+ position: absolute;
63
+ left: auto;
64
+ top: 0;
65
+ right: 0;
66
+ bottom: 0;
67
+ background: var(--pal-bg);
68
+ max-width: var(--ui-sidebar-max-w);
69
+ min-width: var(--ui-sidebar-min-w);
70
+ transform-origin: 100% 50%;
71
+ }
72
+ }
73
+
74
+ .bounce-enter-active {
75
+ animation: slide-right 200ms;
76
+ }
77
+
78
+ .bounce-leave-active {
79
+ animation: slide-right 200ms reverse;
80
+ }
81
+
82
+ @keyframes slide-right {
83
+ 0% {
84
+ transform: translateX(100%) scaleX(1);
85
+ }
86
+ 100% {
87
+ transform: translateX(0);
88
+ }
89
+ }
90
+ </style>
@@ -1,60 +1,98 @@
1
- <template>
2
- <label class="fu-text">
3
- <span v-if="label" class="ui-text_label">{{ label }}</span>
4
- <input
5
- v-bind="$attrs"
6
- :value="modelValue"
7
- class="ui-text_input"
8
- @input="$emit('update:modelValue', $event.target.value)"
9
- />
10
- </label>
11
- </template>
12
-
13
- <script>
14
- console.log('fu-text: loaded')
15
-
16
- export default {
17
- name: 'fu-text',
18
- props: {
19
- modelValue: {
20
- type: [String, Number],
21
- default: '',
22
- },
23
- label: {
24
- type: String,
25
- default: '',
26
- },
27
- },
28
- setup(){
29
- console.log('fu-text: setup!')
30
- },
31
- emits: [ 'update:modelValue' ],
32
- }
33
- </script>
34
- <style lang="scss" scoped>
35
- .fu-text {
36
- display: flex;
37
- align-items: center;
38
- flex-direction: column;
39
- justify-content: left;
40
-
41
- &_label {
42
- display: block;
43
- width: 100%;
44
- }
45
-
46
- &_input {
47
- display: block;
48
- box-sizing: border-box;
49
- width: 100%;
50
- height: var(--ui-height);
51
- border-width: var(--fu-text-border-width);
52
- border-color: var(--fu-text-border-color);
53
- border-style: solid;
54
-
55
- &:focus {
56
- outline: none;
57
- }
58
- }
59
- }
60
- </style>
1
+ <template>
2
+ <label
3
+ class="fu-text"
4
+ :class="{'_disabled': $attrs.disabled !== undefined || $attrs.readOnly !== undefined }"
5
+ v-bind="{ class: $attrs.class }"
6
+ >
7
+ <slot />
8
+ <slot name="left" />
9
+ <input
10
+ v-bind="{...$attrs, class: undefined}"
11
+ :value="modelValue"
12
+ class="fu-text_input"
13
+ @input="$emit('update:modelValue', $event.target.value)"
14
+ >
15
+ <slot name="right" />
16
+ </label>
17
+ </template>
18
+
19
+ <script>
20
+ export default {
21
+ name: 'fu-text',
22
+ props: {
23
+ modelValue: {
24
+ type: [ String, Number ],
25
+ default: '',
26
+ },
27
+ },
28
+ emits: [ 'update:modelValue' ],
29
+ }
30
+ </script>
31
+ <style lang="scss" scoped>
32
+ .fu-text {
33
+ @include typo(200);
34
+
35
+ padding: 0;
36
+ display: flex;
37
+ box-sizing: border-box;
38
+ align-items: center;
39
+ justify-content: stretch;
40
+ border-style: var(--ui-lt-border-style);
41
+ border-width: var(--ui-lt-border-width);
42
+ border-color: var(--ui-pal-lateral);
43
+ border-radius: var(--ui-lt-border-radius);
44
+ transition-duration: 240ms;
45
+ transition-timing-function: ease-in-out;
46
+ transition-property: border-color, box-shadow;
47
+ height: var(--ui-lt-h);
48
+ background: var(--ui-pal-bg);
49
+
50
+ &_input {
51
+ @include typo(200);
52
+ @include spacing-padding(100, 300);
53
+
54
+ color: var(--ui-pal-text);
55
+ caret-color: var(--ui-pal);
56
+ min-height: min(100%);
57
+ border: none;
58
+ outline: none;
59
+ background: transparent;
60
+ box-sizing: border-box;
61
+ flex: 1;
62
+ display: block;
63
+ min-width: 0;
64
+ margin: 0;
65
+
66
+ &::selection {
67
+ background-color: var(--ui-pal);
68
+ color: var(--ui-pal-text-select);
69
+ }
70
+
71
+ &::placeholder {
72
+ color: var(--ui-pal-placeholder);
73
+ }
74
+
75
+ &[disabled], &[read-only] {
76
+ cursor: text;
77
+ color: var(--ui-pal-disabled-border);
78
+ }
79
+ }
80
+
81
+ &:hover {
82
+ outline: none;
83
+ box-shadow: 0 5px 12px -4px rgb(var(--rgb-dark), 0.2);
84
+ }
85
+
86
+ &:focus-within {
87
+ outline: none;
88
+ box-shadow: 0 0 0 0 var(--ui-pal);
89
+ border-color: var(--ui-pal);
90
+ }
91
+
92
+ &._disabled {
93
+ border: var(--ui-lt-border-width) var(--ui-lt-disabled-border-style) var(--ui-pal-disabled-border);
94
+ background: transparent;
95
+ box-shadow: none;
96
+ }
97
+ }
98
+ </style>
@@ -0,0 +1,118 @@
1
+ <template>
2
+ <label
3
+ class="fu-text"
4
+ v-bind="{ class: $attrs.class }"
5
+ :class="{'_disabled': $attrs.disabled !== undefined || $attrs.readOnly !== undefined }"
6
+ >
7
+ <textarea
8
+ ref="textarea"
9
+ v-bind="{...$attrs, class: undefined}"
10
+ :value="modelValue"
11
+ class="fu-text_textarea"
12
+ @input="handleInput"
13
+ />
14
+ </label>
15
+ </template>
16
+
17
+ <script>
18
+ import { onMounted, ref } from 'vue'
19
+
20
+ export default {
21
+ name: 'fu-text',
22
+ props: {
23
+ modelValue: {
24
+ type: [ String, Number ],
25
+ default: '',
26
+ },
27
+ autoResize: { type: Boolean, default: false },
28
+ },
29
+ emits: [ 'update:modelValue' ],
30
+ setup (props, { emit }) {
31
+ const textarea = ref(null)
32
+ const handleInput = async (e) => {
33
+ emit('update:modelValue', e.target.value)
34
+ resize(e.target)
35
+ }
36
+ const resize = (elm) => {
37
+ if (!props.autoResize) return
38
+ elm.style.height = 'auto'
39
+ elm.style.height = `${ elm.scrollHeight }px`
40
+ }
41
+ onMounted(() => resize(textarea.value))
42
+
43
+ return { handleInput, textarea }
44
+ },
45
+ }
46
+ </script>
47
+ <style lang="scss">
48
+ :root {
49
+ --ui-textarea-resize: vertical;
50
+ }
51
+ </style>
52
+
53
+ <style lang="scss" scoped>
54
+ .fu-text {
55
+ @include typo(200);
56
+
57
+ padding: 0;
58
+ display: flex;
59
+ box-sizing: border-box;
60
+ align-items: center;
61
+ justify-content: stretch;
62
+ border-style: var(--ui-lt-border-style);
63
+ border-width: var(--ui-lt-border-width);
64
+ border-color: var(--ui-pal-lateral);
65
+ border-radius: var(--ui-lt-border-radius);
66
+ transition-duration: 240ms;
67
+ transition-timing-function: ease-in-out;
68
+ transition-property: border-color, box-shadow;
69
+ min-height: var(--ui-lt-h);
70
+
71
+ &_textarea {
72
+ @include scrollbar-awesome();
73
+ @include typo(200);
74
+ @include spacing-padding(200);
75
+ @include spacing-margin(100);
76
+
77
+ color: var(--ui-pal-text);
78
+ caret-color: var(--ui-pal);
79
+ border: none;
80
+ outline: none;
81
+ background: transparent;
82
+ box-sizing: border-box;
83
+ flex: 1;
84
+ display: block;
85
+ min-width: 0;
86
+ resize: var(--ui-textarea-resize);
87
+ min-height: var(--ui-lt-h);
88
+ height: var(--ui-lt-h);
89
+
90
+ &::selection {
91
+ background-color: var(--ui-pal);
92
+ color: var(--ui-pal-text-select);
93
+ }
94
+
95
+ &[disabled], &[read-only] {
96
+ cursor: text;
97
+ color: var(--ui-pal-disabled-border);
98
+ }
99
+ }
100
+
101
+ &:hover {
102
+ outline: none;
103
+ box-shadow: 0 5px 12px -4px rgb(var(--rgb-dark), 0.2);
104
+ }
105
+
106
+ &:focus-within {
107
+ outline: none;
108
+ box-shadow: 0 0 0 0 var(--ui-pal);
109
+ border-color: var(--ui-pal);
110
+ }
111
+
112
+ &._disabled {
113
+ border: var(--ui-lt-border-width) var(--ui-lt-disabled-border-style) var(--ui-pal-disabled-border);
114
+ background: transparent;
115
+ box-shadow: none;
116
+ }
117
+ }
118
+ </style>