fu-kit 0.4.2 → 0.6.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.
@@ -0,0 +1,206 @@
1
+ <template>
2
+ <label
3
+ class="ui-check"
4
+ :class="{'_disabled': isDisabled, '_checked': modelValue }"
5
+ v-bind="{ class: $attrs.class, style: $attrs.style }"
6
+ @mouseup="mouseUp"
7
+ >
8
+ <input
9
+ v-bind="{...$attrs, disabled: isDisabled, type: 'checkbox', class: undefined, style: undefined}"
10
+ class="ui-check_input"
11
+ @input="$emit('update:modelValue', $event.target.checked)"
12
+ :checked="modelValue"
13
+ ref="inputRef"
14
+ >
15
+ <span
16
+ class="ui-check_box"
17
+ :class="{
18
+ 'ui-check_switch': switchLike,
19
+ 'ui-check_check': !switchLike,
20
+ _loading: isLoading
21
+ }"
22
+ >
23
+ <svg v-if="!switchLike" class="ui-check_check-icon" viewBox="0 0 24 24">
24
+ <path stroke="var(--ui-pal, currentColor)" stroke-linecap="round" stroke-width="2" d="m7.5 12.5 4 3 5.5-8" />
25
+ </svg>
26
+ </span>
27
+ <slot />
28
+ </label>
29
+ </template>
30
+
31
+ <script>
32
+ import { computed, defineComponent, ref } from 'vue'
33
+
34
+ export default defineComponent({
35
+ name: 'ui-check',
36
+ props: {
37
+ modelValue: { type: Boolean, default: false },
38
+ switchLike: { type: Boolean, default: false },
39
+ thin: { type: Boolean, default: false },
40
+ isLoading: { type: Boolean, default: false },
41
+ disabled: { type: Boolean, default: false },
42
+ },
43
+ emits: [ 'update:modelValue' ],
44
+ setup (props) {
45
+ const inputRef = ref(null)
46
+ const isDisabled = computed(() => props.disabled || props.disabled === '')
47
+
48
+
49
+ const mouseUp = () => {
50
+ // oof
51
+ setTimeout(() => {
52
+ inputRef.value.blur()
53
+ }, 0)
54
+ }
55
+
56
+ return { isDisabled, inputRef, mouseUp }
57
+ },
58
+ })
59
+ </script>
60
+ <style lang="scss" scoped>
61
+ @import "../../scss";
62
+
63
+ .ui-check {
64
+ @include typo(200);
65
+
66
+ user-select: none;
67
+ display: flex;
68
+ box-sizing: border-box;
69
+ align-items: center;
70
+ justify-content: var(--ui-check-justify-content, stretch);
71
+ height: var(--ui-lt-h);
72
+ position: relative;
73
+ outline: none;
74
+ color: var(--pal-text-dimm);
75
+ font-family: var(--typo-font-text);
76
+ cursor: pointer;
77
+ line-height: 1;
78
+
79
+ &_input {
80
+ opacity: 0; // weird, but it's working inside the label
81
+ position: absolute;
82
+ left: 0;
83
+ top: 0;
84
+ pointer-events: none;
85
+ }
86
+
87
+ &_check {
88
+ border-style: var(--ui-lt-border-style);
89
+ border-width: var(--ui-check-border-w);
90
+ border-color: var(--pal-grey700);
91
+ height: var(--ui-check-size);
92
+ width: var(--ui-check-size);
93
+ border-radius: var(--ui-lt-border-radius);
94
+ display: flex;
95
+ justify-content: stretch;
96
+ align-items: stretch;
97
+ margin-right: spacing(200);
98
+ transition: all var(--ui-transition);
99
+
100
+ &-icon {
101
+ transition: all var(--ui-transition);
102
+ transform: scale(0);
103
+ width: 100%;
104
+ }
105
+ }
106
+
107
+ &._checked &_check {
108
+ border-color: var(--ui-pal);
109
+ }
110
+
111
+ &._checked &_check-icon {
112
+ transform: scale(1.4);
113
+ }
114
+
115
+ &_switch {
116
+ width: var(--ui-switch-w);
117
+ border-radius: var(--ui-switch-h);
118
+ background-color: var(--pal-grey700);
119
+ border: var(--ui-lt-border-width) solid var(--pal-grey700);
120
+ margin-right: spacing(200);
121
+ display: flex;
122
+ align-items: center;
123
+ justify-content: flex-start;
124
+ height: var(--ui-switch-h);
125
+
126
+ --switch-left: 0;
127
+
128
+ &:before {
129
+ transform: translateX(var(--switch-left));
130
+ content: "";
131
+ display: block;
132
+ background-color: white;
133
+ border: var(--ui-lt-border-width) solid transparent;
134
+ border-radius: var(--ui-switch-h);
135
+ height: calc(100% - var(--ui-lt-border-width) * 2);
136
+ aspect-ratio: 1;
137
+ transition: all var(--ui-transition);
138
+ }
139
+ }
140
+
141
+ &._checked &_switch {
142
+ background-color: var(--ui-pal);
143
+ border-color: var(--ui-pal);
144
+ }
145
+
146
+ &._checked &_switch:before {
147
+ --switch-left: calc(var(--ui-switch-w) - var(--ui-switch-h));
148
+ transform: translateX(var(--switch-left));
149
+ }
150
+
151
+ &._checked {
152
+ color: var(--pal-text);
153
+ }
154
+
155
+ &:hover:not(&._disabled) &_box {
156
+ box-shadow: 0 5px 12px -4px rgb(var(--rgb-dark), 0.2);
157
+ }
158
+
159
+ &:hover {
160
+ color: var(--pal-text);
161
+ }
162
+
163
+ &:focus-within &_box {
164
+ box-shadow: 0 0 2px 4px rgba(var(--ui-rgb), 0.3);
165
+ }
166
+
167
+ &._disabled {
168
+ color: var(--ui-pal-disabled-border);
169
+ cursor: not-allowed;
170
+ }
171
+
172
+ &._disabled &_check {
173
+ border: var(--ui-lt-border-width) var(--ui-lt-disabled-border-style) var(--ui-pal-disabled-border);
174
+ box-shadow: none;
175
+ }
176
+
177
+ &._disabled &_switch {
178
+ background-color: rgba(var(--rgb-grey700), 0.25);
179
+ border-color: transparent;
180
+ }
181
+
182
+ &._disabled._checked &_switch {
183
+ background-color: rgba(var(--ui-rgb), 0.3);
184
+ }
185
+ }
186
+
187
+ ._loading {
188
+ animation: pulse 1000ms infinite;
189
+ }
190
+
191
+ @keyframes pulse {
192
+ 40% {
193
+ transform: scale(1.1);
194
+ box-shadow: 0 0 0 5px rgba(var(--ui-rgb), 0.3);
195
+ }
196
+
197
+ 80% {
198
+ transform: scale(1);
199
+ box-shadow: 0 0 0 10px rgba(var(--ui-rgb), 0);
200
+ }
201
+
202
+ 100% {
203
+ box-shadow: 0 0 0 0 rgba(var(--ui-rgb), 0)
204
+ }
205
+ }
206
+ </style>
@@ -0,0 +1,133 @@
1
+ <template>
2
+ <div class="code-input">
3
+ <div class="code-input_wrapper">
4
+ <input
5
+ :maxlength="$props.length"
6
+ type="text"
7
+ class="code-input_inp"
8
+ :value="$props.modelValue"
9
+ @input="handleInput"
10
+ ref="inp"
11
+ @focus.passive="selectionUpdate"
12
+ @dragend.passive="selectionUpdate"
13
+ @mouseup.passive="selectionUpdate"
14
+ @keydown.passive="selectionUpdate"
15
+ @keyup.passive="selectionUpdate"
16
+ v-bind="{ ...$attrs }"
17
+ />
18
+ <div
19
+ v-for="i in $props.length"
20
+ :key="'char' + i"
21
+ class="code-input_char"
22
+ :class="{
23
+ '_select': i - 1 >= selection.from && i - 1 < selection.to && selection.from !== selection.to,
24
+ '_cursor': i - 1 === selection.to,
25
+ }"
26
+ >
27
+ <span class="code-input_char-item">{{ chars[i - 1] || '' }}</span>
28
+ </div>
29
+ </div>
30
+ </div>
31
+ </template>
32
+
33
+ <script>
34
+ import { computed, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
35
+
36
+ export default {
37
+ name: 'ui-code-input',
38
+ props: {
39
+ modelValue: {
40
+ type: String,
41
+ },
42
+ length: {
43
+ type: Number,
44
+ default: 6,
45
+ },
46
+ },
47
+ setup (props, { emit }) {
48
+ const selection = reactive({ from: 0, to: 0 })
49
+ const inp = ref(null)
50
+
51
+ const selectionUpdate = (e) => {
52
+ selection.from = e.target.selectionStart
53
+ selection.to = e.target.selectionEnd
54
+ }
55
+
56
+ const handleInput = (e) => {
57
+ emit('update:modelValue', e.target.value)
58
+ }
59
+
60
+ const chars = computed(() => (props.modelValue).split(''))
61
+
62
+ onMounted(() => { })
63
+ onBeforeUnmount(() => { })
64
+
65
+ return { chars, selection, selectionUpdate, inp, handleInput }
66
+ },
67
+ }
68
+ </script>
69
+
70
+ <style lang="scss" scoped>
71
+ .code-input {
72
+ --charbox-width: 34px;
73
+ --chabox-height: 48px;
74
+
75
+ display: flex;
76
+
77
+ &_wrapper {
78
+ position: relative;
79
+ display: flex;
80
+ justify-content: flex-start;
81
+ align-items: center;
82
+ flex-wrap: nowrap;
83
+ gap: spacing(400);
84
+ }
85
+
86
+ &_inp {
87
+ padding: 14px;
88
+ position: absolute;
89
+ width: 100%;
90
+ border: none;
91
+ background: transparent;
92
+ font-family: var(--typo-font-mono);
93
+ font-size: var(--typo-h200);
94
+ letter-spacing: 41px;
95
+ outline: none;
96
+ opacity: 0;
97
+ }
98
+
99
+ &_char {
100
+ width: var(--charbox-width);
101
+ height: var(--ui-lt-h);
102
+ border: 1px solid var(--charbox-border-color, var(--pal-grey300));
103
+ border-radius: var(--ui-lt-border-radius);
104
+ color: var(--charbox-text-color, var(--pal-grey900));
105
+ align-items: center;
106
+ justify-content: center;
107
+ display: flex;
108
+
109
+ &-item {
110
+ border-width: 2px 0;
111
+ border-style: solid;
112
+ border-color: transparent;
113
+ background: transparent;
114
+ min-width: 0.75em;
115
+ min-height: 1.5em;
116
+ text-align: center;
117
+ }
118
+
119
+ &._select &-item {
120
+ color: var(--pal-white);
121
+ background: var(--pal-primary);
122
+ }
123
+
124
+ input:focus ~ &._cursor {
125
+ border-color: var(--ui-pal);
126
+ }
127
+
128
+ input:focus ~ &._cursor &-item {
129
+ border-bottom-color: var(--pal-black);
130
+ }
131
+ }
132
+ }
133
+ </style>
@@ -0,0 +1,63 @@
1
+ <template>
2
+ <div class="ui-code-view">
3
+ <ui-button-link
4
+ v-if="collapse"
5
+ class="ui-code-view_toggle"
6
+ @click="isShown = !isShown"
7
+ >
8
+ {{ isShown ? 'Hide' : 'Show' }} {{ label }}
9
+ </ui-button-link>
10
+ <p class="ui-code-view_title" v-else-if="label">{{ label }}</p>
11
+ <pre v-if="collapse ? isShown : true" class="ui-code-view_pre"><slot /></pre>
12
+ </div>
13
+ </template>
14
+
15
+ <script>
16
+ import { defineComponent, ref, toRaw } from 'vue'
17
+
18
+ import UiButtonLink from './UiButtonLink.vue'
19
+
20
+ export default defineComponent({
21
+ name: 'ui-code-view',
22
+ components: { UiButtonLink },
23
+ props: {
24
+ label: { type: String, default: '' },
25
+ collapse: { type: Boolean, default: null },
26
+ },
27
+ setup (props) {
28
+ const collapse = ref(toRaw(props.collapse))
29
+ const isShown = ref(false)
30
+ const label = props.label
31
+ return { isShown, label, collapse }
32
+ },
33
+ })
34
+ </script>
35
+
36
+ <style scoped lang="scss">
37
+ @import "../../scss";
38
+
39
+ .ui-code-view {
40
+ margin: spacing(200, 0);
41
+
42
+ &_title {
43
+ @include typo(100);
44
+
45
+ font-weight: bold;
46
+ margin-bottom: spacing(200);
47
+ }
48
+
49
+ &_pre {
50
+ @include scrollbar-awesome();
51
+ padding: spacing(300, 200);
52
+
53
+ font-family: var(--typo-font-mono);
54
+ overflow: auto;
55
+ max-width: 100%;
56
+ background: var(--pal-block);
57
+ border: 1px solid var(--pal-block-border);
58
+ border-radius: var(--ui-lt-border-radius);
59
+
60
+ --ui-scroll-bg: var(--pal-block);
61
+ }
62
+ }
63
+ </style>
@@ -0,0 +1,88 @@
1
+ <template>
2
+ <ui-button
3
+ :hollow="!isCopied"
4
+ v-bind="$attrs"
5
+ class="ui-copy"
6
+ @click="handleCopyToClipboard"
7
+ :class="{'_hot': isCopied}"
8
+ >
9
+ <slot />
10
+ </ui-button>
11
+ </template>
12
+
13
+ <script>
14
+ import { defineComponent, onBeforeUnmount, ref } from 'vue'
15
+ import UiButton from './UiButton.vue'
16
+
17
+ export default defineComponent({
18
+ name: 'ui-copy',
19
+ components: { UiButton },
20
+ props: {
21
+ value: { type: [ String, null ], required: true },
22
+ },
23
+ setup (props) {
24
+ const isCopied = ref(false)
25
+ let timeout
26
+
27
+ onBeforeUnmount(() => {
28
+ if (timeout) clearTimeout(timeout)
29
+ })
30
+
31
+ const handleCopyToClipboard = (event) => {
32
+ if (navigator.clipboard) {
33
+ navigator.clipboard.writeText(props.value)
34
+ } else {
35
+ execCopy()
36
+ }
37
+
38
+ isCopied.value = false
39
+ requestAnimationFrame(() => (isCopied.value = true))
40
+
41
+ if (timeout) clearTimeout(timeout)
42
+ timeout = setTimeout(() => isCopied.value = false, 1000)
43
+
44
+ event.target.blur()
45
+ }
46
+
47
+ // fallback
48
+ const execCopy = () => {
49
+ const textArea = document.createElement('textarea')
50
+ textArea.value = props.value
51
+ textArea.setAttribute('readonly', '')
52
+ textArea.style.position = 'absolute'
53
+ textArea.style.left = '-9999px'
54
+ document.body.appendChild(textArea)
55
+ textArea.select()
56
+
57
+ try {
58
+ document.execCommand('copy')
59
+ } catch (err) {
60
+ console.error(err)
61
+ } finally {
62
+ document.body.removeChild(textArea)
63
+ }
64
+ }
65
+
66
+ return {
67
+ handleCopyToClipboard,
68
+ isCopied,
69
+ }
70
+ },
71
+ })
72
+ </script>
73
+
74
+ <style lang="scss" scoped>
75
+ @import "../../scss";
76
+
77
+ .ui-copy {
78
+ padding: spacing(0, 200);
79
+ @include ellipsis();
80
+
81
+ --ui-lt-h: 2em;
82
+ --ui-pal: var(--pal-light);
83
+
84
+ &._hot {
85
+ color: var(--pal-positive);
86
+ }
87
+ }
88
+ </style>
@@ -0,0 +1,109 @@
1
+ <template>
2
+ <div
3
+ class="ui-dropdown"
4
+ :class="{ '_is-open': !disabled && isOpen }"
5
+ v-click-away="isOpen && handleClickaway"
6
+ >
7
+ <div class="ui-dropdown_trigger" @click="toggle">
8
+ <slot :isOpen="!disabled && isOpen" />
9
+ </div>
10
+
11
+ <div v-if="!disabled && isOpen" class="ui-dropdown_content" :class="{ _right: snapToRight }" ref="slotWrap">
12
+ <slot name="content" :dropdownClose="close" />
13
+ </div>
14
+ </div>
15
+ </template>
16
+
17
+ <script>
18
+ // todo: rewrite to composition
19
+
20
+ import { directive as clickAway } from 'vue3-click-away'
21
+
22
+ import { defineComponent } from 'vue'
23
+
24
+ export default defineComponent({
25
+ name: 'ui-dropdown',
26
+ directives: { clickAway },
27
+ props: {
28
+ clickaway: { type: Boolean, default: true },
29
+ snapToRight: { type: Boolean, default: false },
30
+ disabled: { type: Boolean, default: false },
31
+ },
32
+ emits: [ 'open', 'close' ],
33
+ data () {
34
+ return {
35
+ isOpen: false,
36
+ }
37
+ },
38
+
39
+ watch: {
40
+ isOpen (value) {
41
+ this.$emit(value ? 'open' : 'close')
42
+ },
43
+ },
44
+
45
+ methods: {
46
+ handleClickaway (e) {
47
+ /* v-on-click-outside don't recognize slots */
48
+ const path = e.path || (e.composedPath && e.composedPath())
49
+ if (path.includes(this.$refs.slotWrap)) return
50
+
51
+ if (this.clickaway && this.isOpen) {
52
+ this.toggle()
53
+ }
54
+ },
55
+ toggle () {
56
+ if (this.disabled) return
57
+ this.isOpen = !this.isOpen
58
+ },
59
+ close () {
60
+ this.isOpen = false
61
+ },
62
+ },
63
+ })
64
+ </script>
65
+
66
+ <style lang="scss" scoped>
67
+ @import "../../scss";
68
+
69
+ .ui-dropdown {
70
+ @include typo(200);
71
+
72
+ padding: 0;
73
+ box-sizing: border-box;
74
+ position: relative;
75
+
76
+ &_trigger {
77
+ display: flex;
78
+ align-items: center;
79
+ justify-content: stretch;
80
+ }
81
+
82
+ &_content {
83
+ @include scrollbar-awesome();
84
+
85
+ overflow: auto;
86
+ min-width: 100%;
87
+ max-width: 100vw;
88
+ max-height: var(--ui-dropdown-max-height);
89
+ flex-direction: column;
90
+ justify-content: stretch;
91
+ border-radius: var(--ui-lt-border-radius);
92
+ position: absolute;
93
+ top: 100%;
94
+ background: var(--ui-pal-bg);
95
+ margin-top: spacing(200);
96
+ z-index: var(--lt-z-pop);
97
+ background: var(--pal-white);
98
+ box-shadow: 0 3px 11px rgba(var(--rgb-black), 0.2);
99
+
100
+ &:not(._right) {
101
+ left: 0;
102
+ }
103
+
104
+ &._right {
105
+ right: 0;
106
+ }
107
+ }
108
+ }
109
+ </style>
@@ -0,0 +1,66 @@
1
+ <template>
2
+ <div
3
+ class="ui-dropdown-item"
4
+ :class="{
5
+ '_interactive': interactive || autoClose,
6
+ '_active': active,
7
+ }"
8
+ @click="handleClick"
9
+ >
10
+ <slot />
11
+ </div>
12
+ </template>
13
+
14
+ <script>
15
+ import { defineComponent } from 'vue'
16
+
17
+ export default defineComponent({
18
+ name: 'ui-dropdown-item',
19
+ props: {
20
+ autoClose: { type: Boolean, default: false },
21
+ interactive: { type: Boolean, default: false },
22
+ active: { type: Boolean, default: false },
23
+ look: { type: [ String ], default: 'default' },
24
+ },
25
+ methods: {
26
+ handleClick () {
27
+ if (this.autoClose) {
28
+ this.$parent.close()
29
+ }
30
+ },
31
+ },
32
+ })
33
+ </script>
34
+
35
+ <style lang="scss" scoped>
36
+ @import "../../scss";
37
+
38
+ .ui-dropdown-item {
39
+ padding: spacing(200) spacing(400);
40
+ display: flex;
41
+ align-items: center;
42
+ min-height: var(--ui-lt-h);
43
+
44
+ &._interactive {
45
+ color: var(--ui-pal-text);
46
+ cursor: pointer;
47
+
48
+ &:hover {
49
+ color: var(--ui-pal);
50
+ }
51
+
52
+ &:focus,
53
+ &:active {
54
+ outline: none;
55
+ }
56
+ }
57
+
58
+ &._active {
59
+ outline: none;
60
+ color: var(--ui-pal);
61
+
62
+ &:hover {
63
+ }
64
+ }
65
+ }
66
+ </style>
@@ -0,0 +1,29 @@
1
+ <template>
2
+ <svg v-bind="{ ...$attrs }">
3
+ <use :href="`#${name}`"></use>
4
+ </svg>
5
+ </template>
6
+
7
+ <script>
8
+ /**
9
+ * CSS vars:
10
+ * - --ui-color: color of the icon
11
+ * - --ui-size: size of the icon
12
+ */
13
+ export default {
14
+ name: 'ui-icon',
15
+ props: {
16
+ name: { type: [ String ], required: true },
17
+ },
18
+ }
19
+ </script>
20
+
21
+ <style lang="scss" scoped>
22
+ svg {
23
+ display: block;
24
+ height: var(--icon-size, 24px);
25
+ width: var(--icon-size, 24px);
26
+ min-width: var(--icon-size, 24px);
27
+ stroke-width: 2px;
28
+ }
29
+ </style>