Skip to content

Commit 7f2abd1

Browse files
brc-ddkiaking
andauthoredMay 2, 2024
feat(action-list): support showing tooltip and implement disabled prop (#516)
closes #514 closes #515 --------- Co-authored-by: Kia King Ishii <kia.king.08@gmail.com>
1 parent 22e7e16 commit 7f2abd1

19 files changed

+200
-80
lines changed
 

‎docs/.vitepress/theme/components/Showcase.vue

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ const computedStory = computed(() => {
2525
</a>
2626
</div>
2727

28-
<div class="main">
28+
<div class="main vp-raw">
2929
<slot />
3030
</div>
3131
</div>

‎docs/components/action-list.md

+30
Original file line numberDiff line numberDiff line change
@@ -71,9 +71,39 @@ interface ActionListItem {
7171
// prop is set, the item is rendered via `<SLink>`.
7272
link?: string
7373

74+
// Whether the item is disabled.
75+
disabled?: boolean
76+
77+
// The tooltip to be displayed when the item is hovered or focused.
78+
tooltip?: Tooltip
79+
7480
// The callback to be called when the item is clicked.
7581
onClick?(): void
7682
}
83+
84+
interface Tooltip {
85+
// The HTML tag to be used for the tooltip.
86+
// Defaults to `span`.
87+
tag?: string
88+
89+
// The text to be displayed in the tooltip. The tooltip
90+
// will only be visible when this prop is set.
91+
text?: MaybeRef<string | null>
92+
93+
// The position of the tooltip relative to the button.
94+
// Defaults to `top`
95+
position?: 'top' | 'right' | 'bottom' | 'left'
96+
97+
// The trigger to show the tooltip.
98+
// Defaults to `both`
99+
trigger?: 'hover' | 'focus' | 'both'
100+
101+
// The time after which the tooltip is hidden if triggered
102+
// because of focussing the trigger element (in milliseconds).
103+
// Defaults to `undefined` (the tooltip will not hide
104+
// automatically).
105+
timeout?: number
106+
}
77107
```
78108

79109
```vue-html

‎docs/components/avatar-stack.md

+20
Original file line numberDiff line numberDiff line change
@@ -115,3 +115,23 @@ interface Props {
115115
]"
116116
/>
117117
```
118+
119+
### `:tooltip`
120+
121+
Whether to display a tooltip when hovering over the component. You can also pass an object with the `position` property to define the position of the tooltip. The default position is `top`. The tooltip will display the name of the user if provided.
122+
123+
```ts
124+
interface Props {
125+
tooltip?: boolean | { position?: 'top' | 'right' | 'bottom' | 'left' }
126+
}
127+
```
128+
129+
```vue-html
130+
<SAvatarStack
131+
tooltip
132+
:avatars="[
133+
{ image: '/path/to/image-1.jpg', name: 'Jane Doe' },
134+
{ image: '/path/to/image-2.jpg', name: 'Richard Roe' }
135+
]"
136+
/>
137+
```

‎docs/components/avatar.md

+14
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,17 @@ interface Props {
8080
```vue-html
8181
<SAvatar name="John Doe" />
8282
```
83+
84+
### `:tooltip`
85+
86+
When set to `true`, a tooltip showing the name will be displayed when hovering over the avatar. You can also pass an object to define the position of the tooltip.
87+
88+
```ts
89+
interface Props {
90+
tooltip?: boolean | { position?: 'top' | 'right' | 'bottom' | 'left' }
91+
}
92+
```
93+
94+
```vue-html
95+
<SAvatar tooltip name="John Doe" />
96+
```

‎docs/components/button.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -302,8 +302,8 @@ interface Props {
302302
// Defaults to `both`
303303
trigger?: 'hover' | 'focus' | 'both'
304304

305-
// Defines the timeout in milliseconds to hide the tooltip.
306-
// Used only when `trigger` is set to `'focus'` or `'both'`.
305+
// The time after which the tooltip is hidden if triggered
306+
// because of focussing the trigger element (in milliseconds).
307307
// Defaults to `undefined` (the tooltip will not hide
308308
// automatically).
309309
timeout?: number

‎docs/components/tooltip.md

+18
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,24 @@ interface Props {
7070
</STooltip>
7171
```
7272

73+
### `:triggerTag`
74+
75+
Defines the HTML tag for the trigger element. Any value passed to this prop will used as `<component :is="triggerTag">`. The default tag for the trigger element is `span`.
76+
77+
```ts
78+
interface Props {
79+
triggerTag?: string
80+
}
81+
```
82+
83+
```vue-html
84+
<STooltip triggerTag="div" text="...">
85+
...
86+
</STooltip>
87+
```
88+
89+
Note that setting this to something other than `span` is required when you want to put block elements within the trigger element.
90+
7391
### `:text`
7492

7593
Defines the content of tooltip.

‎lib/components/SActionList.vue

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ defineProps<{
1818
:label="item.label"
1919
:text="item.text"
2020
:link="item.link"
21+
:disabled="item.disabled"
22+
:tooltip="item.tooltip"
2123
:on-click="item.onClick"
2224
/>
2325
</div>

‎lib/components/SActionListItem.vue

+26-44
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
<script setup lang="ts">
22
import { type IconifyIcon } from '@iconify/vue/dist/offline'
33
import { computed } from 'vue'
4-
import SIcon from './SIcon.vue'
5-
import SLink from './SLink.vue'
4+
import SButton, { type Tooltip } from './SButton.vue'
65
76
export interface ActionListItem {
87
leadIcon?: IconifyIcon
98
link?: string
109
label?: string
1110
disabled?: boolean
12-
onClick?(): void
11+
tooltip?: Tooltip
12+
onClick?: () => void
1313
1414
/** @deprecated Use `:label` instead. */
1515
text?: string
@@ -20,54 +20,36 @@ const props = defineProps<ActionListItem>()
2020
const _label = computed(() => {
2121
return props.label ?? props.text
2222
})
23+
24+
const _tooltip = computed<Tooltip | undefined>(() => {
25+
return props.tooltip ? { display: 'block', ...props.tooltip } : undefined
26+
})
2327
</script>
2428

2529
<template>
26-
<component
27-
:is="link ? SLink : 'button'"
28-
class="SActionList"
29-
:href="link"
30-
@click="() => onClick?.()"
31-
>
32-
<span v-if="leadIcon" class="lead-icon">
33-
<SIcon class="lead-icon-svg" :icon="leadIcon" />
34-
</span>
35-
<span class="text">{{ _label }}</span>
36-
</component>
30+
<div class="SActionListItem">
31+
<SButton
32+
block
33+
size="small"
34+
type="text"
35+
:icon="leadIcon"
36+
:label="_label"
37+
:href="link"
38+
:disabled="disabled"
39+
:tooltip="_tooltip"
40+
@click="onClick"
41+
/>
42+
</div>
3743
</template>
3844

3945
<style scoped lang="postcss">
40-
.SActionList {
41-
display: flex;
42-
gap: 8px;
43-
border-radius: 6px;
44-
padding: 4px 8px;
45-
width: 100%;
46-
text-align: left;
47-
line-height: 24px;
48-
font-size: 14px;
49-
transition: background-color 0.25s;
50-
51-
&:hover {
52-
background-color: var(--c-bg-mute-1);
53-
}
46+
.SActionListItem {
47+
--button-font-size: 14px;
5448
55-
&:active {
56-
background-color: var(--c-bg-mute-2);
57-
transition: background-color 0.1s;
49+
:deep(.SButton),
50+
:slotted(.SButton) {
51+
justify-content: flex-start;
52+
font-weight: 400;
5853
}
5954
}
60-
61-
.lead-icon {
62-
display: flex;
63-
align-items: center;
64-
height: 24px;
65-
flex-shrink: 0;
66-
color: var(--c-text-2);
67-
}
68-
69-
.lead-icon-svg {
70-
width: 16px;
71-
height: 16px;
72-
}
7355
</style>

‎lib/components/SAvatar.vue

+26-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
<script setup lang="ts">
22
import { computed } from 'vue'
3+
import { type Position } from '../composables/Tooltip'
4+
import SFragment from './SFragment.vue'
5+
import STooltip from './STooltip.vue'
36
47
export type Size =
58
| 'nano'
@@ -14,6 +17,7 @@ const props = defineProps<{
1417
size?: Size
1518
avatar?: string | null
1619
name?: string | null
20+
tooltip?: boolean | { position?: Position }
1721
}>()
1822
1923
const classes = computed(() => [
@@ -22,29 +26,46 @@ const classes = computed(() => [
2226
])
2327
2428
const initial = computed(() => props.name?.charAt(0).toUpperCase())
29+
30+
const tooltipPosition = computed(() => {
31+
return (props.tooltip && typeof props.tooltip === 'object')
32+
? props.tooltip.position || 'top'
33+
: 'top'
34+
})
2535
</script>
2636

2737
<template>
28-
<div class="SAvatar" :class="classes">
29-
<img v-if="avatar" class="img" :src="avatar">
30-
<p v-else-if="initial" class="initial">{{ initial }}</p>
31-
</div>
38+
<SFragment
39+
:is="tooltip && name && STooltip"
40+
:text="name"
41+
:position="tooltipPosition"
42+
display="block"
43+
tag="div"
44+
trigger-tag="div"
45+
>
46+
<div class="SAvatar" :class="classes">
47+
<img v-if="avatar" class="img" :src="avatar">
48+
<p v-else-if="initial" class="initial">{{ initial }}</p>
49+
</div>
50+
</SFragment>
3251
</template>
3352

3453
<style lang="postcss" scoped>
3554
.SAvatar {
3655
display: flex;
3756
justify-content: center;
3857
align-items: center;
39-
border-radius: 50%;
4058
background-color: var(--c-bg-elv-1);
59+
border-radius: 50%;
4160
overflow: hidden;
4261
}
4362
4463
.img {
4564
object-fit: cover;
4665
height: 100%;
4766
width: 100%;
67+
border-radius: 50%;
68+
overflow: hidden;
4869
}
4970
5071
.initial {

‎lib/components/SAvatarStack.vue

+14-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<script setup lang="ts">
22
import { computed } from 'vue'
3+
import { type Position } from '../composables/Tooltip'
34
import SAvatar from './SAvatar.vue'
45
56
export type Size = 'mini' | 'small' | 'medium' | 'large' | 'jumbo'
@@ -8,6 +9,7 @@ const props = withDefaults(defineProps<{
89
size?: Size
910
avatars: { image?: string; name?: string }[]
1011
avatarCount?: number
12+
tooltip?: boolean | { position?: Position }
1113
}>(), {
1214
size: 'medium',
1315
avatarCount: 2
@@ -30,6 +32,7 @@ const count = computed(() => {
3032
:size="size"
3133
:avatar="avatar.image"
3234
:name="avatar.name"
35+
:tooltip="tooltip"
3336
/>
3437
<div v-if="count" class="more">+{{ count }}</div>
3538
</div>
@@ -39,16 +42,18 @@ const count = computed(() => {
3942
.SAvatarStack {
4043
display: flex;
4144
42-
> * {
43-
border: 2px solid var(--c-bg-elv-2);
45+
:slotted(.SAvatar), :deep(.SAvatar), .more {
4446
flex-shrink: 0;
47+
border: 2px solid var(--c-bg-elv-2);
48+
border-radius: 50%;
49+
overflow: hidden;
4550
}
4651
47-
&.mini > *:not(:last-child) { margin-right: -6px }
48-
&.small > *:not(:last-child) { margin-right: -8px }
49-
&.medium > *:not(:last-child) { margin-right: -8px }
50-
&.large > *:not(:last-child) { margin-right: -12px }
51-
&.jumbo > *:not(:last-child) { margin-right: -16px }
52+
&.mini > :deep(*):not(:last-child) { margin-right: -6px }
53+
&.small > :deep(*):not(:last-child) { margin-right: -8px }
54+
&.medium > :deep(*):not(:last-child) { margin-right: -8px }
55+
&.large > :deep(*):not(:last-child) { margin-right: -12px }
56+
&.jumbo > :deep(*):not(:last-child) { margin-right: -16px }
5257
}
5358
5459
.more {
@@ -61,6 +66,8 @@ const count = computed(() => {
6166
border-radius: 50%;
6267
line-height: 1;
6368
color: var(--c-text-2);
69+
z-index: 1;
70+
height: 100%;
6471
6572
.mini & { font-size: 10px }
6673
.small & { font-size: 10px }

0 commit comments

Comments
 (0)