webcake-ui-kit 1.0.17 → 1.0.18
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 +7 -1
- package/src/components/avatar/Avatar.vue +54 -0
- package/src/components/avatar/avatar.css +91 -0
- package/src/components/avatar-stack/AvatarStack.vue +79 -0
- package/src/components/avatar-stack/avatar-stack.css +110 -0
- package/src/components/button/button.css +5 -5
- package/src/components/divider/divider.css +0 -1
- package/src/components/empty/Empty.vue +55 -0
- package/src/components/empty/empty.css +80 -0
- package/src/components/empty-icon/EmptyIcon.vue +16 -0
- package/src/components/empty-icon/empty-icon.css +30 -0
- package/src/components/field/Field.vue +82 -0
- package/src/components/field/field.css +87 -0
- package/src/components/tooltip/Tooltip.vue +149 -0
- package/src/components/tooltip/tooltip.css +102 -0
- package/src/index.js +6 -0
- package/src/styles/color_general.css +4 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-ui-kit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.18",
|
|
4
4
|
"description": "UI Kit for Vue 2 && 3 - Pure Options API",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
"./WkAccordion": "./src/components/accordion/Accordion.vue",
|
|
19
19
|
"./WkAccordionItem": "./src/components/accordion-item/AccordionItem.vue",
|
|
20
20
|
"./WkAlertDialog": "./src/components/alert-dialog/AlertDialog.vue",
|
|
21
|
+
"./WkAvatar": "./src/components/avatar/Avatar.vue",
|
|
22
|
+
"./WkAvatarStack": "./src/components/avatar-stack/AvatarStack.vue",
|
|
21
23
|
"./WkBadge": "./src/components/badge/Badge.vue",
|
|
22
24
|
"./WkBreadcrumb": "./src/components/breadcrumb/Breadcrumb.vue",
|
|
23
25
|
"./WkButton": "./src/components/button/Button.vue",
|
|
@@ -26,6 +28,9 @@
|
|
|
26
28
|
"./WkCheckboxGroup": "./src/components/checkbox-group/CheckboxGroup.vue",
|
|
27
29
|
"./WkDialog": "./src/components/dialog/Dialog.vue",
|
|
28
30
|
"./WkDivider": "./src/components/divider/Divider.vue",
|
|
31
|
+
"./WkEmpty": "./src/components/empty/Empty.vue",
|
|
32
|
+
"./WkEmptyIcon": "./src/components/empty-icon/EmptyIcon.vue",
|
|
33
|
+
"./WkField": "./src/components/field/Field.vue",
|
|
29
34
|
"./WkInput": "./src/components/input/Input.vue",
|
|
30
35
|
"./WkPagination": "./src/components/pagination/Pagination.vue",
|
|
31
36
|
"./WkRadio": "./src/components/radio/Radio.vue",
|
|
@@ -44,6 +49,7 @@
|
|
|
44
49
|
"./WkTag": "./src/components/tag/Tag.vue",
|
|
45
50
|
"./WkToggle": "./src/components/toggle/Toggle.vue",
|
|
46
51
|
"./WkToggleGroup": "./src/components/toggle-group/ToggleGroup.vue",
|
|
52
|
+
"./WkTooltip": "./src/components/tooltip/Tooltip.vue",
|
|
47
53
|
"./WkTypography": "./src/components/typography/Typography.vue",
|
|
48
54
|
"./components/*": "./src/components/*",
|
|
49
55
|
"./icons": "./src/icons/index.js",
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span :class="['ui-avatar', `ui-avatar--${size}`, `ui-avatar--${roundness}`]">
|
|
3
|
+
<img v-if="showImage" class="ui-avatar__image" :src="src" :alt="alt" @error="onImageError" />
|
|
4
|
+
<span v-else class="ui-avatar__fallback">
|
|
5
|
+
<slot>{{ name }}</slot>
|
|
6
|
+
</span>
|
|
7
|
+
<span v-if="online" class="ui-avatar__indicator" aria-hidden="true"></span>
|
|
8
|
+
</span>
|
|
9
|
+
</template>
|
|
10
|
+
|
|
11
|
+
<script>
|
|
12
|
+
export default {
|
|
13
|
+
name: 'Avatar',
|
|
14
|
+
props: {
|
|
15
|
+
size: {
|
|
16
|
+
type: String,
|
|
17
|
+
default: 'regular',
|
|
18
|
+
validator: v => ['regular', 'small', 'tiny', 'extra-tiny'].includes(v)
|
|
19
|
+
},
|
|
20
|
+
roundness: {
|
|
21
|
+
type: String,
|
|
22
|
+
default: 'round',
|
|
23
|
+
validator: v => ['round', 'roundrect'].includes(v)
|
|
24
|
+
},
|
|
25
|
+
src: { type: String, default: '' },
|
|
26
|
+
alt: { type: String, default: '' },
|
|
27
|
+
name: { type: String, default: '' },
|
|
28
|
+
online: { type: Boolean, default: false }
|
|
29
|
+
},
|
|
30
|
+
emits: [],
|
|
31
|
+
data() {
|
|
32
|
+
return {
|
|
33
|
+
imageError: false
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
computed: {
|
|
37
|
+
showImage() {
|
|
38
|
+
return !!this.src && !this.imageError
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
watch: {
|
|
42
|
+
src() {
|
|
43
|
+
this.imageError = false
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
methods: {
|
|
47
|
+
onImageError() {
|
|
48
|
+
this.imageError = true
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
</script>
|
|
53
|
+
|
|
54
|
+
<style src="./avatar.css" scoped></style>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
.ui-avatar {
|
|
2
|
+
position: relative;
|
|
3
|
+
display: inline-flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: center;
|
|
6
|
+
flex-shrink: 0;
|
|
7
|
+
background: var(--accent-bg);
|
|
8
|
+
color: var(--primary-fg);
|
|
9
|
+
font-family: var(--font-family-body);
|
|
10
|
+
font-weight: var(--paragraph-bold-font-weight);
|
|
11
|
+
letter-spacing: var(--paragraph-small-letter-spacing);
|
|
12
|
+
vertical-align: middle;
|
|
13
|
+
overflow: visible;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/* Sizes — match Figma frame widths exactly */
|
|
17
|
+
.ui-avatar--regular {
|
|
18
|
+
width: 40px;
|
|
19
|
+
height: 40px;
|
|
20
|
+
font-size: var(--paragraph-small-font-size);
|
|
21
|
+
line-height: var(--paragraph-small-line-height);
|
|
22
|
+
}
|
|
23
|
+
.ui-avatar--small {
|
|
24
|
+
width: 32px;
|
|
25
|
+
height: 32px;
|
|
26
|
+
font-size: var(--paragraph-mini-font-size);
|
|
27
|
+
line-height: var(--paragraph-mini-line-height);
|
|
28
|
+
}
|
|
29
|
+
.ui-avatar--tiny {
|
|
30
|
+
width: 24px;
|
|
31
|
+
height: 24px;
|
|
32
|
+
font-size: 10px;
|
|
33
|
+
line-height: 16px;
|
|
34
|
+
}
|
|
35
|
+
.ui-avatar--extra-tiny {
|
|
36
|
+
width: 20px;
|
|
37
|
+
height: 20px;
|
|
38
|
+
font-size: 9px;
|
|
39
|
+
line-height: 12px;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* Roundness */
|
|
43
|
+
.ui-avatar--round {
|
|
44
|
+
border-radius: var(--rounded-full);
|
|
45
|
+
}
|
|
46
|
+
.ui-avatar--roundrect {
|
|
47
|
+
border-radius: var(--rounded-lg);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* Image */
|
|
51
|
+
.ui-avatar__image {
|
|
52
|
+
display: block;
|
|
53
|
+
width: 100%;
|
|
54
|
+
height: 100%;
|
|
55
|
+
object-fit: cover;
|
|
56
|
+
border-radius: inherit;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/* Fallback (initials / custom slot) */
|
|
60
|
+
.ui-avatar__fallback {
|
|
61
|
+
display: inline-flex;
|
|
62
|
+
align-items: center;
|
|
63
|
+
justify-content: center;
|
|
64
|
+
width: 100%;
|
|
65
|
+
height: 100%;
|
|
66
|
+
text-align: center;
|
|
67
|
+
user-select: none;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/* Online indicator — auto-sized per avatar size, 2px white ring punches out of avatar */
|
|
71
|
+
.ui-avatar__indicator {
|
|
72
|
+
position: absolute;
|
|
73
|
+
right: 0;
|
|
74
|
+
bottom: 0;
|
|
75
|
+
border-radius: var(--rounded-full);
|
|
76
|
+
background: var(--positive-500);
|
|
77
|
+
box-shadow: 0 0 0 2px var(--primary-bg);
|
|
78
|
+
}
|
|
79
|
+
.ui-avatar--regular .ui-avatar__indicator {
|
|
80
|
+
width: 10px;
|
|
81
|
+
height: 10px;
|
|
82
|
+
}
|
|
83
|
+
.ui-avatar--small .ui-avatar__indicator {
|
|
84
|
+
width: 8px;
|
|
85
|
+
height: 8px;
|
|
86
|
+
}
|
|
87
|
+
.ui-avatar--tiny .ui-avatar__indicator,
|
|
88
|
+
.ui-avatar--extra-tiny .ui-avatar__indicator {
|
|
89
|
+
width: 6px;
|
|
90
|
+
height: 6px;
|
|
91
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span :class="['ui-avatar-stack', `ui-avatar-stack--${size}`]">
|
|
3
|
+
<Avatar
|
|
4
|
+
v-for="(item, idx) in visibleItems"
|
|
5
|
+
:key="`avatar-${idx}`"
|
|
6
|
+
:size="size"
|
|
7
|
+
:src="item.src || ''"
|
|
8
|
+
:alt="item.alt || ''"
|
|
9
|
+
:name="item.name || ''"
|
|
10
|
+
:class="itemClasses"
|
|
11
|
+
/>
|
|
12
|
+
<Avatar v-if="overflowCount > 0" :size="size" :class="[...itemClasses, 'ui-avatar-stack__overflow']">
|
|
13
|
+
<slot name="overflow" v-bind="{ count: overflowCount }">{{ formattedOverflow }}</slot>
|
|
14
|
+
</Avatar>
|
|
15
|
+
</span>
|
|
16
|
+
</template>
|
|
17
|
+
|
|
18
|
+
<script>
|
|
19
|
+
import Avatar from '../avatar/Avatar.vue'
|
|
20
|
+
|
|
21
|
+
export default {
|
|
22
|
+
name: 'AvatarStack',
|
|
23
|
+
components: { Avatar },
|
|
24
|
+
props: {
|
|
25
|
+
size: {
|
|
26
|
+
type: String,
|
|
27
|
+
default: 'regular',
|
|
28
|
+
validator: v => ['regular', 'small'].includes(v)
|
|
29
|
+
},
|
|
30
|
+
items: {
|
|
31
|
+
type: Array,
|
|
32
|
+
default: () => []
|
|
33
|
+
},
|
|
34
|
+
max: {
|
|
35
|
+
type: Number,
|
|
36
|
+
default: 0
|
|
37
|
+
},
|
|
38
|
+
overflowLabel: {
|
|
39
|
+
type: Function,
|
|
40
|
+
default: null
|
|
41
|
+
},
|
|
42
|
+
animation: {
|
|
43
|
+
type: String,
|
|
44
|
+
default: 'none',
|
|
45
|
+
validator: v => ['none', 'pulse', 'bounce', 'ring'].includes(v)
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
emits: [],
|
|
49
|
+
computed: {
|
|
50
|
+
visibleItems() {
|
|
51
|
+
if (this.max > 0 && this.items.length > this.max) {
|
|
52
|
+
return this.items.slice(0, this.max)
|
|
53
|
+
}
|
|
54
|
+
return this.items
|
|
55
|
+
},
|
|
56
|
+
overflowCount() {
|
|
57
|
+
if (this.max > 0 && this.items.length > this.max) {
|
|
58
|
+
return this.items.length - this.max
|
|
59
|
+
}
|
|
60
|
+
return 0
|
|
61
|
+
},
|
|
62
|
+
formattedOverflow() {
|
|
63
|
+
if (typeof this.overflowLabel === 'function') {
|
|
64
|
+
return this.overflowLabel(this.overflowCount)
|
|
65
|
+
}
|
|
66
|
+
return '+' + this.overflowCount
|
|
67
|
+
},
|
|
68
|
+
itemClasses() {
|
|
69
|
+
const classes = ['ui-avatar-stack__item']
|
|
70
|
+
if (this.animation !== 'none') {
|
|
71
|
+
classes.push(`ui-avatar-stack__item--animation-${this.animation}`)
|
|
72
|
+
}
|
|
73
|
+
return classes
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
</script>
|
|
78
|
+
|
|
79
|
+
<style src="./avatar-stack.css" scoped></style>
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
.ui-avatar-stack {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
vertical-align: middle;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/* Each child avatar overlaps the previous one and shows a white ring to
|
|
8
|
+
"punch out" of its neighbours. Box-shadow follows border-radius and does
|
|
9
|
+
not take up layout space, so the avatar dimensions stay intact. */
|
|
10
|
+
.ui-avatar-stack__item {
|
|
11
|
+
position: relative;
|
|
12
|
+
margin-left: -8px;
|
|
13
|
+
box-shadow: 0 0 0 2px var(--primary-bg);
|
|
14
|
+
}
|
|
15
|
+
.ui-avatar-stack__item:first-child {
|
|
16
|
+
margin-left: 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/* Stacking order: earlier siblings sit above later ones so the leftmost
|
|
20
|
+
avatar's ring is visible over its neighbour. */
|
|
21
|
+
.ui-avatar-stack__item {
|
|
22
|
+
z-index: 1;
|
|
23
|
+
}
|
|
24
|
+
.ui-avatar-stack__item:nth-child(1) {
|
|
25
|
+
z-index: 5;
|
|
26
|
+
}
|
|
27
|
+
.ui-avatar-stack__item:nth-child(2) {
|
|
28
|
+
z-index: 4;
|
|
29
|
+
}
|
|
30
|
+
.ui-avatar-stack__item:nth-child(3) {
|
|
31
|
+
z-index: 3;
|
|
32
|
+
}
|
|
33
|
+
.ui-avatar-stack__item:nth-child(4) {
|
|
34
|
+
z-index: 2;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Overflow chip — same shape as siblings, slightly darker background. */
|
|
38
|
+
.ui-avatar-stack__overflow {
|
|
39
|
+
background: var(--secondary-bg);
|
|
40
|
+
color: var(--secondary-fg);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* ============================ Animations ============================
|
|
44
|
+
Three loop animations the consumer opts into via the `animation` prop
|
|
45
|
+
on AvatarStack. Each animation is applied to every visible avatar
|
|
46
|
+
child (including the "+N" overflow chip). All honour
|
|
47
|
+
`prefers-reduced-motion: reduce`. */
|
|
48
|
+
|
|
49
|
+
@keyframes ui-avatar-stack-pulse {
|
|
50
|
+
0%,
|
|
51
|
+
100% {
|
|
52
|
+
transform: scale(1);
|
|
53
|
+
opacity: 1;
|
|
54
|
+
}
|
|
55
|
+
50% {
|
|
56
|
+
transform: scale(0.94);
|
|
57
|
+
opacity: 0.78;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
@keyframes ui-avatar-stack-bounce {
|
|
62
|
+
0%,
|
|
63
|
+
100% {
|
|
64
|
+
transform: translateY(0);
|
|
65
|
+
}
|
|
66
|
+
50% {
|
|
67
|
+
transform: translateY(-4px);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
@keyframes ui-avatar-stack-ring {
|
|
72
|
+
0% {
|
|
73
|
+
transform: scale(1);
|
|
74
|
+
opacity: 0.8;
|
|
75
|
+
}
|
|
76
|
+
100% {
|
|
77
|
+
transform: scale(1.35);
|
|
78
|
+
opacity: 0;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.ui-avatar-stack__item--animation-pulse {
|
|
83
|
+
animation: ui-avatar-stack-pulse 1.6s ease-in-out infinite;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.ui-avatar-stack__item--animation-bounce {
|
|
87
|
+
animation: ui-avatar-stack-bounce 1.2s ease-in-out infinite;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.ui-avatar-stack__item--animation-ring::after {
|
|
91
|
+
content: '';
|
|
92
|
+
position: absolute;
|
|
93
|
+
inset: -3px;
|
|
94
|
+
border-radius: inherit;
|
|
95
|
+
border: 2px solid var(--positive-500);
|
|
96
|
+
pointer-events: none;
|
|
97
|
+
animation: ui-avatar-stack-ring 1.6s ease-out infinite;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
@media (prefers-reduced-motion: reduce) {
|
|
101
|
+
.ui-avatar-stack__item--animation-pulse,
|
|
102
|
+
.ui-avatar-stack__item--animation-bounce {
|
|
103
|
+
animation: none;
|
|
104
|
+
}
|
|
105
|
+
.ui-avatar-stack__item--animation-ring::after {
|
|
106
|
+
animation: none;
|
|
107
|
+
opacity: 0.6;
|
|
108
|
+
transform: scale(1.05);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
letter-spacing: var(--paragraph-mini-letter-spacing);
|
|
44
44
|
}
|
|
45
45
|
.ui-btn--xs .ui-btn__icon,
|
|
46
|
-
.ui-btn--xs
|
|
46
|
+
.ui-btn--xs ::v-deep svg {
|
|
47
47
|
width: 16px;
|
|
48
48
|
height: 16px;
|
|
49
49
|
}
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
letter-spacing: var(--paragraph-small-letter-spacing);
|
|
58
58
|
}
|
|
59
59
|
.ui-btn--sm .ui-btn__icon,
|
|
60
|
-
.ui-btn--sm
|
|
60
|
+
.ui-btn--sm ::v-deep svg {
|
|
61
61
|
width: 20px;
|
|
62
62
|
height: 20px;
|
|
63
63
|
}
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
letter-spacing: var(--paragraph-small-letter-spacing);
|
|
72
72
|
}
|
|
73
73
|
.ui-btn--md .ui-btn__icon,
|
|
74
|
-
.ui-btn--md
|
|
74
|
+
.ui-btn--md ::v-deep svg {
|
|
75
75
|
width: 20px;
|
|
76
76
|
height: 20px;
|
|
77
77
|
}
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
letter-spacing: var(--paragraph-small-letter-spacing);
|
|
86
86
|
}
|
|
87
87
|
.ui-btn--lg .ui-btn__icon,
|
|
88
|
-
.ui-btn--lg
|
|
88
|
+
.ui-btn--lg ::v-deep svg {
|
|
89
89
|
width: 20px;
|
|
90
90
|
height: 20px;
|
|
91
91
|
}
|
|
@@ -99,7 +99,7 @@
|
|
|
99
99
|
letter-spacing: var(--paragraph-regular-letter-spacing);
|
|
100
100
|
}
|
|
101
101
|
.ui-btn--xl .ui-btn__icon,
|
|
102
|
-
.ui-btn--xl
|
|
102
|
+
.ui-btn--xl ::v-deep svg {
|
|
103
103
|
width: 20px;
|
|
104
104
|
height: 20px;
|
|
105
105
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div :class="['ui-empty', `ui-empty--${variant}`]">
|
|
3
|
+
<div class="ui-empty__content">
|
|
4
|
+
<div v-if="hasMedia" class="ui-empty__media">
|
|
5
|
+
<slot name="media"></slot>
|
|
6
|
+
</div>
|
|
7
|
+
<div v-if="hasTextBlock" class="ui-empty__text">
|
|
8
|
+
<p v-if="hasTitle" class="ui-empty__title">
|
|
9
|
+
<slot name="title">{{ title }}</slot>
|
|
10
|
+
</p>
|
|
11
|
+
<p v-if="hasDescription" class="ui-empty__description">
|
|
12
|
+
<slot name="description">{{ description }}</slot>
|
|
13
|
+
</p>
|
|
14
|
+
</div>
|
|
15
|
+
<div v-if="hasFooter" class="ui-empty__footer">
|
|
16
|
+
<slot></slot>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script>
|
|
23
|
+
export default {
|
|
24
|
+
name: 'Empty',
|
|
25
|
+
props: {
|
|
26
|
+
variant: {
|
|
27
|
+
type: String,
|
|
28
|
+
default: 'default',
|
|
29
|
+
validator: v => ['default', 'outline', 'background', 'outline-dashed'].includes(v)
|
|
30
|
+
},
|
|
31
|
+
title: { type: String, default: '' },
|
|
32
|
+
description: { type: String, default: '' }
|
|
33
|
+
},
|
|
34
|
+
emits: [],
|
|
35
|
+
computed: {
|
|
36
|
+
hasMedia() {
|
|
37
|
+
return !!((this.$scopedSlots && this.$scopedSlots.media) || this.$slots.media)
|
|
38
|
+
},
|
|
39
|
+
hasTitle() {
|
|
40
|
+
return !!this.title || !!((this.$scopedSlots && this.$scopedSlots.title) || this.$slots.title)
|
|
41
|
+
},
|
|
42
|
+
hasDescription() {
|
|
43
|
+
return !!this.description || !!((this.$scopedSlots && this.$scopedSlots.description) || this.$slots.description)
|
|
44
|
+
},
|
|
45
|
+
hasTextBlock() {
|
|
46
|
+
return this.hasTitle || this.hasDescription
|
|
47
|
+
},
|
|
48
|
+
hasFooter() {
|
|
49
|
+
return !!((this.$scopedSlots && this.$scopedSlots.default) || this.$slots.default)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<style src="./empty.css" scoped></style>
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
.ui-empty {
|
|
2
|
+
display: flex;
|
|
3
|
+
flex-direction: column;
|
|
4
|
+
align-items: stretch;
|
|
5
|
+
padding: var(--spacing-2xl);
|
|
6
|
+
border-radius: var(--rounded-2xl);
|
|
7
|
+
width: 100%;
|
|
8
|
+
box-sizing: border-box;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/* Variants */
|
|
12
|
+
.ui-empty--default {
|
|
13
|
+
background: transparent;
|
|
14
|
+
border: 0;
|
|
15
|
+
}
|
|
16
|
+
.ui-empty--outline {
|
|
17
|
+
background: transparent;
|
|
18
|
+
border: 1px solid var(--border-primary);
|
|
19
|
+
}
|
|
20
|
+
.ui-empty--background {
|
|
21
|
+
background: var(--accent-bg);
|
|
22
|
+
border: 0;
|
|
23
|
+
}
|
|
24
|
+
.ui-empty--outline-dashed {
|
|
25
|
+
background: transparent;
|
|
26
|
+
border: 1px dashed var(--border-primary);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/* Inner content stack */
|
|
30
|
+
.ui-empty__content {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-direction: column;
|
|
33
|
+
align-items: center;
|
|
34
|
+
gap: var(--spacing-md);
|
|
35
|
+
width: 100%;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.ui-empty__media {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: center;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.ui-empty__text {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
gap: var(--spacing-2xs);
|
|
48
|
+
text-align: center;
|
|
49
|
+
width: 100%;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.ui-empty__title {
|
|
53
|
+
margin: 0;
|
|
54
|
+
font-family: var(--font-family-body);
|
|
55
|
+
font-size: var(--paragraph-regular-font-size);
|
|
56
|
+
line-height: var(--paragraph-regular-line-height);
|
|
57
|
+
letter-spacing: var(--paragraph-regular-letter-spacing);
|
|
58
|
+
font-weight: var(--paragraph-medium-font-weight);
|
|
59
|
+
color: var(--primary-fg);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.ui-empty__description {
|
|
63
|
+
margin: 0;
|
|
64
|
+
font-family: var(--font-family-body);
|
|
65
|
+
font-size: var(--paragraph-small-font-size);
|
|
66
|
+
line-height: var(--paragraph-small-line-height);
|
|
67
|
+
letter-spacing: var(--paragraph-small-letter-spacing);
|
|
68
|
+
font-weight: var(--paragraph-font-weight);
|
|
69
|
+
color: var(--muted-fg);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/* Footer slot — stacks action group(s) below the text block.
|
|
73
|
+
Consumer composes button rows / link rows freely; gap matches the
|
|
74
|
+
outer content stack so a single child OR multiple children line up. */
|
|
75
|
+
.ui-empty__footer {
|
|
76
|
+
display: flex;
|
|
77
|
+
flex-direction: column;
|
|
78
|
+
align-items: center;
|
|
79
|
+
gap: var(--spacing-md);
|
|
80
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span class="ui-empty-icon">
|
|
3
|
+
<span class="ui-empty-icon__frame">
|
|
4
|
+
<slot></slot>
|
|
5
|
+
</span>
|
|
6
|
+
</span>
|
|
7
|
+
</template>
|
|
8
|
+
|
|
9
|
+
<script>
|
|
10
|
+
export default {
|
|
11
|
+
name: 'EmptyIcon',
|
|
12
|
+
emits: []
|
|
13
|
+
}
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<style src="./empty-icon.css" scoped></style>
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
.ui-empty-icon {
|
|
2
|
+
display: inline-flex;
|
|
3
|
+
align-items: center;
|
|
4
|
+
justify-content: center;
|
|
5
|
+
width: 40px;
|
|
6
|
+
height: 40px;
|
|
7
|
+
padding: var(--spacing-xs);
|
|
8
|
+
background: var(--secondary-bg);
|
|
9
|
+
border-radius: var(--rounded-lg);
|
|
10
|
+
box-sizing: border-box;
|
|
11
|
+
flex-shrink: 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* Inner frame fixes the icon at the Figma-specified 24x24. Consumers
|
|
15
|
+
place their <svg> (or <img>) directly in the default slot; the frame
|
|
16
|
+
normalises sizing so different icons line up. */
|
|
17
|
+
.ui-empty-icon__frame {
|
|
18
|
+
display: inline-flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: center;
|
|
21
|
+
width: 24px;
|
|
22
|
+
height: 24px;
|
|
23
|
+
color: var(--primary-fg);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.ui-empty-icon__frame ::v-deep svg {
|
|
27
|
+
width: 100%;
|
|
28
|
+
height: 100%;
|
|
29
|
+
display: block;
|
|
30
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
:class="[
|
|
4
|
+
'ui-field',
|
|
5
|
+
`ui-field--${layout}`,
|
|
6
|
+
layout === 'horizontal' && `ui-field--align-${align}`,
|
|
7
|
+
required && 'ui-field--required',
|
|
8
|
+
isError && 'ui-field--error'
|
|
9
|
+
]"
|
|
10
|
+
>
|
|
11
|
+
<div
|
|
12
|
+
v-if="hasLabel"
|
|
13
|
+
class="ui-field__label"
|
|
14
|
+
:style="layout === 'horizontal' ? { flexBasis: normalizedLabelWidth, width: normalizedLabelWidth } : null"
|
|
15
|
+
>
|
|
16
|
+
<slot name="label">{{ label }}</slot>
|
|
17
|
+
</div>
|
|
18
|
+
<div class="ui-field__body">
|
|
19
|
+
<div class="ui-field__control">
|
|
20
|
+
<slot></slot>
|
|
21
|
+
</div>
|
|
22
|
+
<div v-if="hasMessage" class="ui-field__message">
|
|
23
|
+
<span class="ui-field__message-icon" aria-hidden="true">
|
|
24
|
+
<slot name="message-icon">
|
|
25
|
+
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
26
|
+
<circle cx="8" cy="8" r="6.25" stroke="currentColor" stroke-width="1.3" />
|
|
27
|
+
<path d="M8 7.25v3.75" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
|
|
28
|
+
<circle cx="8" cy="5.25" r="0.75" fill="currentColor" />
|
|
29
|
+
</svg>
|
|
30
|
+
</slot>
|
|
31
|
+
</span>
|
|
32
|
+
<span class="ui-field__message-text">
|
|
33
|
+
<slot name="message">{{ isError ? errorText : helpText }}</slot>
|
|
34
|
+
</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</template>
|
|
39
|
+
|
|
40
|
+
<script>
|
|
41
|
+
export default {
|
|
42
|
+
name: 'Field',
|
|
43
|
+
props: {
|
|
44
|
+
layout: {
|
|
45
|
+
type: String,
|
|
46
|
+
default: 'vertical',
|
|
47
|
+
validator: v => ['vertical', 'horizontal'].includes(v)
|
|
48
|
+
},
|
|
49
|
+
label: { type: String, default: '' },
|
|
50
|
+
required: { type: Boolean, default: false },
|
|
51
|
+
helpText: { type: String, default: '' },
|
|
52
|
+
errorText: { type: String, default: '' },
|
|
53
|
+
labelWidth: { type: [String, Number], default: '120px' },
|
|
54
|
+
align: {
|
|
55
|
+
type: String,
|
|
56
|
+
default: 'center',
|
|
57
|
+
validator: v => ['start', 'center'].includes(v)
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
emits: [],
|
|
61
|
+
computed: {
|
|
62
|
+
hasLabel() {
|
|
63
|
+
return !!this.label || !!((this.$scopedSlots && this.$scopedSlots.label) || this.$slots.label)
|
|
64
|
+
},
|
|
65
|
+
isError() {
|
|
66
|
+
return !!this.errorText
|
|
67
|
+
},
|
|
68
|
+
hasMessage() {
|
|
69
|
+
return (
|
|
70
|
+
!!this.errorText ||
|
|
71
|
+
!!this.helpText ||
|
|
72
|
+
!!((this.$scopedSlots && this.$scopedSlots.message) || this.$slots.message)
|
|
73
|
+
)
|
|
74
|
+
},
|
|
75
|
+
normalizedLabelWidth() {
|
|
76
|
+
return typeof this.labelWidth === 'number' ? `${this.labelWidth}px` : this.labelWidth
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
</script>
|
|
81
|
+
|
|
82
|
+
<style src="./field.css" scoped></style>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
.ui-field {
|
|
2
|
+
display: flex;
|
|
3
|
+
gap: var(--spacing-2xs);
|
|
4
|
+
width: 100%;
|
|
5
|
+
box-sizing: border-box;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/* Vertical: label on top, body below */
|
|
9
|
+
.ui-field--vertical {
|
|
10
|
+
flex-direction: column;
|
|
11
|
+
align-items: stretch;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/* Horizontal: label on the left, body on the right */
|
|
15
|
+
.ui-field--horizontal {
|
|
16
|
+
flex-direction: row;
|
|
17
|
+
}
|
|
18
|
+
.ui-field--horizontal.ui-field--align-center {
|
|
19
|
+
align-items: center;
|
|
20
|
+
}
|
|
21
|
+
.ui-field--horizontal.ui-field--align-start {
|
|
22
|
+
align-items: flex-start;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/* Label */
|
|
26
|
+
.ui-field__label {
|
|
27
|
+
flex-shrink: 0;
|
|
28
|
+
font-family: var(--font-family-body);
|
|
29
|
+
font-size: var(--paragraph-small-font-size);
|
|
30
|
+
line-height: var(--paragraph-small-line-height);
|
|
31
|
+
letter-spacing: var(--paragraph-small-letter-spacing);
|
|
32
|
+
font-weight: var(--paragraph-medium-font-weight);
|
|
33
|
+
color: var(--primary-fg);
|
|
34
|
+
}
|
|
35
|
+
.ui-field--required .ui-field__label {
|
|
36
|
+
color: var(--destructive-text);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* Body wraps the control + inline message so both align under the label
|
|
40
|
+
column in horizontal layout, and stack tightly in vertical layout. */
|
|
41
|
+
.ui-field__body {
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
gap: var(--spacing-2xs);
|
|
45
|
+
flex: 1 1 0;
|
|
46
|
+
min-width: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.ui-field__control {
|
|
50
|
+
width: 100%;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Inline message: 16px icon + text. Defaults to muted; turns destructive on error. */
|
|
54
|
+
.ui-field__message {
|
|
55
|
+
display: flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
gap: var(--spacing-xs);
|
|
58
|
+
font-family: var(--font-family-body);
|
|
59
|
+
font-size: var(--paragraph-small-font-size);
|
|
60
|
+
line-height: var(--paragraph-small-line-height);
|
|
61
|
+
letter-spacing: var(--paragraph-small-letter-spacing);
|
|
62
|
+
font-weight: var(--paragraph-font-weight);
|
|
63
|
+
color: var(--muted-fg);
|
|
64
|
+
}
|
|
65
|
+
.ui-field--error .ui-field__message {
|
|
66
|
+
color: var(--destructive-text);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.ui-field__message-icon {
|
|
70
|
+
display: inline-flex;
|
|
71
|
+
align-items: center;
|
|
72
|
+
justify-content: center;
|
|
73
|
+
width: 16px;
|
|
74
|
+
height: 16px;
|
|
75
|
+
flex-shrink: 0;
|
|
76
|
+
color: inherit;
|
|
77
|
+
}
|
|
78
|
+
.ui-field__message-icon ::v-deep svg {
|
|
79
|
+
width: 100%;
|
|
80
|
+
height: 100%;
|
|
81
|
+
display: block;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.ui-field__message-text {
|
|
85
|
+
flex: 1 1 0;
|
|
86
|
+
min-width: 0;
|
|
87
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<span class="ui-tooltip">
|
|
3
|
+
<span
|
|
4
|
+
ref="trigger"
|
|
5
|
+
class="ui-tooltip__trigger"
|
|
6
|
+
@mouseenter="onShow"
|
|
7
|
+
@mouseleave="onHide"
|
|
8
|
+
@focusin="onShow"
|
|
9
|
+
@focusout="onHide"
|
|
10
|
+
>
|
|
11
|
+
<slot></slot>
|
|
12
|
+
</span>
|
|
13
|
+
<span
|
|
14
|
+
ref="tooltip"
|
|
15
|
+
v-show="isVisible"
|
|
16
|
+
:class="[
|
|
17
|
+
'ui-tooltip__content',
|
|
18
|
+
`ui-tooltip__content--${color}`,
|
|
19
|
+
`ui-tooltip__content--${side}`,
|
|
20
|
+
hasMaxWidth && 'ui-tooltip__content--wrap'
|
|
21
|
+
]"
|
|
22
|
+
:style="tooltipStyle"
|
|
23
|
+
role="tooltip"
|
|
24
|
+
>
|
|
25
|
+
<span class="ui-tooltip__text">
|
|
26
|
+
<slot name="content">{{ title }}</slot>
|
|
27
|
+
</span>
|
|
28
|
+
<span v-if="arrow" :class="['ui-tooltip__arrow', `ui-tooltip__arrow--${side}`]" aria-hidden="true"></span>
|
|
29
|
+
</span>
|
|
30
|
+
</span>
|
|
31
|
+
</template>
|
|
32
|
+
|
|
33
|
+
<script>
|
|
34
|
+
const GAP = 4
|
|
35
|
+
|
|
36
|
+
export default {
|
|
37
|
+
name: 'Tooltip',
|
|
38
|
+
props: {
|
|
39
|
+
side: {
|
|
40
|
+
type: String,
|
|
41
|
+
default: 'top',
|
|
42
|
+
validator: v => ['top', 'bottom', 'left', 'right'].includes(v)
|
|
43
|
+
},
|
|
44
|
+
title: { type: String, default: '' },
|
|
45
|
+
maxWidth: { type: [String, Number], default: '' },
|
|
46
|
+
open: { type: Boolean, default: false },
|
|
47
|
+
color: {
|
|
48
|
+
type: String,
|
|
49
|
+
default: 'default',
|
|
50
|
+
validator: v => ['default', 'brand', 'destructive'].includes(v)
|
|
51
|
+
},
|
|
52
|
+
arrow: { type: Boolean, default: true }
|
|
53
|
+
},
|
|
54
|
+
emits: [],
|
|
55
|
+
data() {
|
|
56
|
+
return {
|
|
57
|
+
hovered: false,
|
|
58
|
+
positionStyle: {}
|
|
59
|
+
}
|
|
60
|
+
},
|
|
61
|
+
computed: {
|
|
62
|
+
isVisible() {
|
|
63
|
+
return this.open || this.hovered
|
|
64
|
+
},
|
|
65
|
+
hasMaxWidth() {
|
|
66
|
+
return this.maxWidth !== '' && this.maxWidth !== null && this.maxWidth !== undefined
|
|
67
|
+
},
|
|
68
|
+
tooltipStyle() {
|
|
69
|
+
const style = Object.assign({}, this.positionStyle)
|
|
70
|
+
if (this.hasMaxWidth) {
|
|
71
|
+
style.maxWidth = typeof this.maxWidth === 'number' ? `${this.maxWidth}px` : this.maxWidth
|
|
72
|
+
}
|
|
73
|
+
return style
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
watch: {
|
|
77
|
+
isVisible(v) {
|
|
78
|
+
if (v) this.$nextTick(this.updatePosition)
|
|
79
|
+
},
|
|
80
|
+
side() {
|
|
81
|
+
if (this.isVisible) this.$nextTick(this.updatePosition)
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
mounted() {
|
|
85
|
+
if (typeof document !== 'undefined' && this.$refs.tooltip) {
|
|
86
|
+
document.body.appendChild(this.$refs.tooltip)
|
|
87
|
+
}
|
|
88
|
+
window.addEventListener('scroll', this.onScrollOrResize, true)
|
|
89
|
+
window.addEventListener('resize', this.onScrollOrResize)
|
|
90
|
+
if (this.open) this.$nextTick(this.updatePosition)
|
|
91
|
+
if (typeof this.$on === 'function') {
|
|
92
|
+
// eslint-disable-next-line vue/no-deprecated-events-api
|
|
93
|
+
this.$on('hook:beforeDestroy', this.cleanup)
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
beforeUnmount() {
|
|
97
|
+
this.cleanup()
|
|
98
|
+
},
|
|
99
|
+
methods: {
|
|
100
|
+
onShow() {
|
|
101
|
+
this.hovered = true
|
|
102
|
+
},
|
|
103
|
+
onHide() {
|
|
104
|
+
this.hovered = false
|
|
105
|
+
},
|
|
106
|
+
onScrollOrResize() {
|
|
107
|
+
if (this.isVisible) this.updatePosition()
|
|
108
|
+
},
|
|
109
|
+
updatePosition() {
|
|
110
|
+
const trigger = this.$refs.trigger
|
|
111
|
+
const tip = this.$refs.tooltip
|
|
112
|
+
if (!trigger || !tip) return
|
|
113
|
+
const t = trigger.getBoundingClientRect()
|
|
114
|
+
const tw = tip.offsetWidth
|
|
115
|
+
const th = tip.offsetHeight
|
|
116
|
+
let top = 0
|
|
117
|
+
let left = 0
|
|
118
|
+
if (this.side === 'top') {
|
|
119
|
+
top = t.top - th - GAP
|
|
120
|
+
left = t.left + (t.width - tw) / 2
|
|
121
|
+
} else if (this.side === 'bottom') {
|
|
122
|
+
top = t.bottom + GAP
|
|
123
|
+
left = t.left + (t.width - tw) / 2
|
|
124
|
+
} else if (this.side === 'left') {
|
|
125
|
+
top = t.top + (t.height - th) / 2
|
|
126
|
+
left = t.left - tw - GAP
|
|
127
|
+
} else {
|
|
128
|
+
top = t.top + (t.height - th) / 2
|
|
129
|
+
left = t.right + GAP
|
|
130
|
+
}
|
|
131
|
+
this.positionStyle = {
|
|
132
|
+
position: 'fixed',
|
|
133
|
+
top: top + 'px',
|
|
134
|
+
left: left + 'px',
|
|
135
|
+
zIndex: 1050
|
|
136
|
+
}
|
|
137
|
+
},
|
|
138
|
+
cleanup() {
|
|
139
|
+
window.removeEventListener('scroll', this.onScrollOrResize, true)
|
|
140
|
+
window.removeEventListener('resize', this.onScrollOrResize)
|
|
141
|
+
if (this.$refs.tooltip && this.$refs.tooltip.parentNode === document.body) {
|
|
142
|
+
document.body.removeChild(this.$refs.tooltip)
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
</script>
|
|
148
|
+
|
|
149
|
+
<style src="./tooltip.css" scoped></style>
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
/* Wrapper just anchors the trigger inline. The content bubble is moved
|
|
2
|
+
out to <body> at mount time so it can never be clipped by overflow
|
|
3
|
+
on an ancestor. Position is computed in JS. */
|
|
4
|
+
.ui-tooltip {
|
|
5
|
+
display: inline-flex;
|
|
6
|
+
vertical-align: middle;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.ui-tooltip__trigger {
|
|
10
|
+
display: inline-flex;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Color tokens cascade via a local CSS variable so the arrow inherits
|
|
14
|
+
the same background and stays in sync with the bubble. */
|
|
15
|
+
.ui-tooltip__content--default {
|
|
16
|
+
--_tooltip-color-bg: var(--tooltip);
|
|
17
|
+
--_tooltip-color-fg: var(--tooltip-foreground);
|
|
18
|
+
}
|
|
19
|
+
.ui-tooltip__content--brand {
|
|
20
|
+
--_tooltip-color-bg: var(--primary-brand-bg);
|
|
21
|
+
--_tooltip-color-fg: var(--inverse-fg);
|
|
22
|
+
}
|
|
23
|
+
.ui-tooltip__content--destructive {
|
|
24
|
+
--_tooltip-color-bg: var(--destructive);
|
|
25
|
+
--_tooltip-color-fg: var(--destructive-inverse-fg);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.ui-tooltip__content {
|
|
29
|
+
display: inline-flex;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: var(--spacing-xs);
|
|
32
|
+
padding: var(--spacing-6) var(--spacing-xs);
|
|
33
|
+
background: var(--_tooltip-color-bg);
|
|
34
|
+
color: var(--_tooltip-color-fg);
|
|
35
|
+
font-family: var(--font-family-body);
|
|
36
|
+
font-size: var(--paragraph-mini-font-size);
|
|
37
|
+
line-height: var(--paragraph-mini-line-height);
|
|
38
|
+
letter-spacing: var(--paragraph-mini-letter-spacing);
|
|
39
|
+
font-weight: var(--paragraph-font-weight);
|
|
40
|
+
border-radius: var(--rounded-lg);
|
|
41
|
+
white-space: nowrap;
|
|
42
|
+
pointer-events: none;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.ui-tooltip__content--wrap {
|
|
46
|
+
white-space: normal;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.ui-tooltip__text {
|
|
50
|
+
display: inline-block;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Arrow as a CSS triangle. The pointing border uses the inherited
|
|
54
|
+
color variable so it always matches the bubble background. */
|
|
55
|
+
.ui-tooltip__arrow {
|
|
56
|
+
position: absolute;
|
|
57
|
+
width: 0;
|
|
58
|
+
height: 0;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.ui-tooltip__arrow--top {
|
|
62
|
+
bottom: -5px;
|
|
63
|
+
left: 50%;
|
|
64
|
+
transform: translateX(-50%);
|
|
65
|
+
border-left: 6px solid transparent;
|
|
66
|
+
border-right: 6px solid transparent;
|
|
67
|
+
border-top: 5px solid var(--_tooltip-color-bg);
|
|
68
|
+
}
|
|
69
|
+
.ui-tooltip__arrow--bottom {
|
|
70
|
+
top: -5px;
|
|
71
|
+
left: 50%;
|
|
72
|
+
transform: translateX(-50%);
|
|
73
|
+
border-left: 6px solid transparent;
|
|
74
|
+
border-right: 6px solid transparent;
|
|
75
|
+
border-bottom: 5px solid var(--_tooltip-color-bg);
|
|
76
|
+
}
|
|
77
|
+
.ui-tooltip__arrow--left {
|
|
78
|
+
right: -5px;
|
|
79
|
+
top: 50%;
|
|
80
|
+
transform: translateY(-50%);
|
|
81
|
+
border-top: 6px solid transparent;
|
|
82
|
+
border-bottom: 6px solid transparent;
|
|
83
|
+
border-left: 5px solid var(--_tooltip-color-bg);
|
|
84
|
+
}
|
|
85
|
+
.ui-tooltip__arrow--right {
|
|
86
|
+
left: -5px;
|
|
87
|
+
top: 50%;
|
|
88
|
+
transform: translateY(-50%);
|
|
89
|
+
border-top: 6px solid transparent;
|
|
90
|
+
border-bottom: 6px solid transparent;
|
|
91
|
+
border-right: 5px solid var(--_tooltip-color-bg);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/* The bubble itself needs to be position-context for the arrow.
|
|
95
|
+
We rely on the inline `position: fixed` set by the positioning logic
|
|
96
|
+
in JS, but we also want consumers to be able to apply a manual
|
|
97
|
+
transform if needed — the arrow's position uses absolute, which is
|
|
98
|
+
fine because the bubble's fixed position establishes a containing
|
|
99
|
+
block for its descendants. */
|
|
100
|
+
.ui-tooltip__content {
|
|
101
|
+
position: relative;
|
|
102
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export { default as WkAccordion } from './components/accordion/Accordion.vue'
|
|
2
2
|
export { default as WkAccordionItem } from './components/accordion-item/AccordionItem.vue'
|
|
3
3
|
export { default as WkAlertDialog } from './components/alert-dialog/AlertDialog.vue'
|
|
4
|
+
export { default as WkAvatar } from './components/avatar/Avatar.vue'
|
|
5
|
+
export { default as WkAvatarStack } from './components/avatar-stack/AvatarStack.vue'
|
|
4
6
|
export { default as WkBadge } from './components/badge/Badge.vue'
|
|
5
7
|
export { default as WkBreadcrumb } from './components/breadcrumb/Breadcrumb.vue'
|
|
6
8
|
export { default as WkButton } from './components/button/Button.vue'
|
|
@@ -9,6 +11,9 @@ export { default as WkCheckbox } from './components/checkbox/Checkbox.vue'
|
|
|
9
11
|
export { default as WkCheckboxGroup } from './components/checkbox-group/CheckboxGroup.vue'
|
|
10
12
|
export { default as WkDialog } from './components/dialog/Dialog.vue'
|
|
11
13
|
export { default as WkDivider } from './components/divider/Divider.vue'
|
|
14
|
+
export { default as WkEmpty } from './components/empty/Empty.vue'
|
|
15
|
+
export { default as WkEmptyIcon } from './components/empty-icon/EmptyIcon.vue'
|
|
16
|
+
export { default as WkField } from './components/field/Field.vue'
|
|
12
17
|
export { default as WkInput } from './components/input/Input.vue'
|
|
13
18
|
export { default as WkPagination } from './components/pagination/Pagination.vue'
|
|
14
19
|
export { default as WkRadio } from './components/radio/Radio.vue'
|
|
@@ -27,4 +32,5 @@ export { default as WkTabs } from './components/tabs/Tabs.vue'
|
|
|
27
32
|
export { default as WkTag } from './components/tag/Tag.vue'
|
|
28
33
|
export { default as WkToggle } from './components/toggle/Toggle.vue'
|
|
29
34
|
export { default as WkToggleGroup } from './components/toggle-group/ToggleGroup.vue'
|
|
35
|
+
export { default as WkTooltip } from './components/tooltip/Tooltip.vue'
|
|
30
36
|
export { default as WkTypography } from './components/typography/Typography.vue'
|
|
@@ -79,6 +79,8 @@
|
|
|
79
79
|
--button-black: var(--color-neutral-950);
|
|
80
80
|
--button-black-fg: var(--color-white-alpha-95);
|
|
81
81
|
--button-black-hover: var(--color-neutral-700);
|
|
82
|
+
--tooltip: var(--color-black-alpha-100);
|
|
83
|
+
--tooltip-foreground: var(--color-white-alpha-100);
|
|
82
84
|
--card: var(--color-white-alpha-100);
|
|
83
85
|
--card-foreground: var(--color-neutral-950);
|
|
84
86
|
|
|
@@ -184,6 +186,8 @@
|
|
|
184
186
|
--button-black: var(--color-neutral-50);
|
|
185
187
|
--button-black-fg: var(--color-black-alpha-95);
|
|
186
188
|
--button-black-hover: var(--color-neutral-200);
|
|
189
|
+
--tooltip: var(--color-white-alpha-100);
|
|
190
|
+
--tooltip-foreground: var(--color-black-alpha-100);
|
|
187
191
|
--card: var(--color-neutral-900);
|
|
188
192
|
--card-foreground: var(--color-white-alpha-100);
|
|
189
193
|
|