Skip to content

Commit e84dc36

Browse files
Merge pull request #95 from appwrite/feat-hover-list
feat: hover list
2 parents df5a5e2 + 2d5c347 commit e84dc36

File tree

5 files changed

+246
-106
lines changed

5 files changed

+246
-106
lines changed

src/lib/components/drop.svelte

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<script lang="ts" context="module">
2+
export type Placement = 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
3+
</script>
4+
5+
<script lang="ts">
6+
import { createPopper, type Instance } from '@popperjs/core';
7+
import { onDestroy, onMount } from 'svelte';
8+
9+
export let show = false;
10+
export let noArrow = false;
11+
export let placement: Placement = 'bottom-start';
12+
export let childStart = false;
13+
14+
let element: HTMLDivElement;
15+
let tooltip: HTMLDivElement;
16+
let arrow: HTMLDivElement;
17+
let instance: Instance;
18+
19+
onMount(() => {
20+
instance = createPopper(element, tooltip, {
21+
placement,
22+
modifiers: [
23+
{
24+
name: 'arrow',
25+
options: {
26+
element: arrow
27+
}
28+
},
29+
{
30+
name: 'offset',
31+
options: {
32+
offset: [0, noArrow ? 0 : 6]
33+
}
34+
},
35+
{
36+
name: 'flip',
37+
options: {
38+
fallbackPlacements: ['bottom-start', 'bottom-end', 'top-start', 'top-end']
39+
}
40+
}
41+
]
42+
});
43+
});
44+
45+
$: if (show) {
46+
instance?.update();
47+
}
48+
49+
onDestroy(() => {
50+
instance?.destroy();
51+
});
52+
53+
const onBlur = (event: MouseEvent) => {
54+
if (
55+
show &&
56+
!(
57+
event.target === element ||
58+
element.contains(event.target as Node) ||
59+
event.target === tooltip ||
60+
tooltip.contains(event.target as Node)
61+
)
62+
) {
63+
show = false;
64+
}
65+
};
66+
</script>
67+
68+
<svelte:window on:click={onBlur} />
69+
70+
<div class="drop-wrapper" class:u-cross-child-start={childStart} bind:this={element}>
71+
<slot />
72+
</div>
73+
74+
<div class="drop-tooltip" bind:this={tooltip} style="z-index: 10">
75+
<div class="drop-arrow" class:u-hide={!show || (show && noArrow)} bind:this={arrow} />
76+
{#if show}
77+
<slot name="list" />
78+
{/if}
79+
</div>
80+
81+
<style global lang="scss">
82+
.drop-tooltip[data-popper-placement^='top'] > .drop-arrow {
83+
bottom: -4px;
84+
}
85+
86+
.drop-tooltip[data-popper-placement^='bottom'] > .drop-arrow {
87+
top: -4px;
88+
}
89+
90+
.drop-tooltip[data-popper-placement^='left'] > .drop-arrow {
91+
right: -4px;
92+
}
93+
94+
.drop-tooltip[data-popper-placement^='right'] > .drop-arrow {
95+
left: -4px;
96+
}
97+
.drop-arrow,
98+
.drop-arrow::before {
99+
position: absolute;
100+
width: 8px;
101+
height: 8px;
102+
z-index: -1;
103+
}
104+
105+
.drop-arrow::before {
106+
content: '';
107+
transform: rotate(45deg);
108+
background: hsl(var(--color-neutral-200));
109+
110+
body.theme-light & {
111+
background: hsl(var(--color-neutral-10));
112+
}
113+
}
114+
</style>

src/lib/components/dropList.svelte

Lines changed: 7 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,17 @@
11
<script lang="ts">
2-
import { createPopper, type Instance } from '@popperjs/core';
3-
import { onDestroy, onMount } from 'svelte';
2+
import { Drop } from '.';
3+
import type { Placement } from './drop.svelte';
44
55
export let show = false;
6-
export let noArrow = false;
76
export let placement: Placement = 'bottom-start';
87
export let scrollable = false;
98
export let childStart = false;
10-
11-
type Placement = 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
12-
let element: HTMLDivElement;
13-
let tooltip: HTMLDivElement;
14-
let arrow: HTMLDivElement;
15-
let instance: Instance;
16-
17-
onMount(() => {
18-
instance = createPopper(element, tooltip, {
19-
placement,
20-
modifiers: [
21-
{
22-
name: 'arrow',
23-
options: {
24-
element: arrow
25-
}
26-
},
27-
{
28-
name: 'offset',
29-
options: {
30-
offset: [0, noArrow ? 0 : 6]
31-
}
32-
},
33-
{
34-
name: 'flip',
35-
options: {
36-
fallbackPlacements: ['top-start', 'top-end', 'bottom-start', 'bottom-end']
37-
}
38-
}
39-
]
40-
});
41-
});
42-
43-
$: if (show) {
44-
instance?.update();
45-
}
46-
47-
onDestroy(() => {
48-
instance?.destroy();
49-
});
50-
51-
const onBlur = (event: MouseEvent) => {
52-
if (show && !(event.target === element || element.contains(event.target as Node))) {
53-
show = false;
54-
}
55-
};
9+
export let noArrow = false;
5610
</script>
5711

58-
<svelte:window on:click={onBlur} />
59-
60-
<div class="drop-wrapper" class:u-cross-child-start={childStart} bind:this={element}>
12+
<Drop bind:show {placement} {childStart} {noArrow}>
6113
<slot />
62-
</div>
63-
64-
<div class="drop-tooltip" bind:this={tooltip} style="z-index: 10">
65-
<div class="drop-arrow" class:u-hide={!show} bind:this={arrow} />
66-
{#if show}
14+
<svelte:fragment slot="list">
6715
<div class="drop is-no-arrow" style="position: revert">
6816
<section
6917
class:u-overflow-y-auto={scrollable}
@@ -75,40 +23,5 @@
7523
</section>
7624
<slot name="other" />
7725
</div>
78-
{/if}
79-
</div>
80-
81-
<style global lang="scss">
82-
.drop-tooltip[data-popper-placement^='top'] > .drop-arrow {
83-
bottom: -4px;
84-
}
85-
86-
.drop-tooltip[data-popper-placement^='bottom'] > .drop-arrow {
87-
top: -4px;
88-
}
89-
90-
.drop-tooltip[data-popper-placement^='left'] > .drop-arrow {
91-
right: -4px;
92-
}
93-
94-
.drop-tooltip[data-popper-placement^='right'] > .drop-arrow {
95-
left: -4px;
96-
}
97-
.drop-arrow,
98-
.drop-arrow::before {
99-
position: absolute;
100-
width: 8px;
101-
height: 8px;
102-
z-index: -1;
103-
}
104-
105-
.drop-arrow::before {
106-
content: '';
107-
transform: rotate(45deg);
108-
background: hsl(var(--color-neutral-200));
109-
110-
body.theme-light & {
111-
background: hsl(var(--color-neutral-10));
112-
}
113-
}
114-
</style>
26+
</svelte:fragment>
27+
</Drop>

src/lib/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export { default as List } from './list.svelte';
1414
export { default as ListItem } from './listItem.svelte';
1515
export { default as Empty } from './empty.svelte';
1616
export { default as EmptySearch } from './emptySearch.svelte';
17+
export { default as Drop } from './drop.svelte';
1718
export { default as DropList } from './dropList.svelte';
1819
export { default as DropListItem } from './dropListItem.svelte';
1920
export { default as DropListLink } from './dropListLink.svelte';
Lines changed: 124 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,33 @@
11
<script lang="ts">
2+
import { tooltip } from '$lib/actions/tooltip';
3+
import { sdkForProject } from '$lib/stores/sdk';
4+
import type { Models } from '@aw-labs/appwrite-console';
5+
import { tick } from 'svelte';
6+
import { AvatarInitials } from '../';
7+
import Output from '../output.svelte';
8+
29
export let role: string;
10+
11+
let content: HTMLDivElement;
12+
let data = null;
13+
14+
async function getData(
15+
permission: string
16+
): Promise<Partial<Models.User<Record<string, unknown>> & Models.Team>> {
17+
const role = permission.split(':')[0];
18+
const id = permission.split(':')[1].split('/')[0];
19+
if (role === 'user') {
20+
const user = await sdkForProject.users.get(id);
21+
return user;
22+
}
23+
if (role === 'team') {
24+
const team = await sdkForProject.teams.get(id);
25+
return team;
26+
}
27+
}
328
</script>
429

5-
<div class="u-flex u-cross-center u-gap-8">
30+
<div class="u-flex u-cross-center u-gap-8 tippy-user">
631
<div>
732
{#if role === 'users'}
833
<div>Users</div>
@@ -11,7 +36,104 @@
1136
{:else if role === 'any'}
1237
<div>Any</div>
1338
{:else}
14-
<div class="u-trim-1">{role}</div>
39+
<div
40+
class="u-trim-1"
41+
use:tooltip={{
42+
interactive: true,
43+
allowHTML: true,
44+
onTrigger(instance) {
45+
instance.hide();
46+
getData(role)
47+
.then((n) => {
48+
data = n;
49+
})
50+
.finally(() => {
51+
tick().then(() => {
52+
instance.setContent(content.innerHTML);
53+
instance.show();
54+
});
55+
});
56+
}
57+
}}>
58+
{role}
59+
</div>
60+
<div class="u-hide" bind:this={content}>
61+
{#if data}
62+
{@const isUser = role.startsWith('user')}
63+
{@const isTeam = role.startsWith('team')}
64+
<div class="user-profile">
65+
<AvatarInitials name={data.name} size={40} />
66+
<span class="user-profile-info is-only-desktop">
67+
<span class="name">
68+
{data.name
69+
? data.name
70+
: data?.email
71+
? data?.email
72+
: data?.phone
73+
? data?.phone
74+
: '-'}
75+
</span>
76+
<Output value={data.$id}>{role}</Output>
77+
</span>
78+
{#if (isUser && (data?.email || data?.phone)) || isTeam}
79+
<span class="user-profile-sep" />
80+
81+
<span class="user-profile-empty-column" />
82+
<span class="user-profile-info is-only-desktop">
83+
{#if isUser}
84+
<p class="text u-x-small">{data?.email}</p>
85+
<p class="text u-x-small">{data?.phone}</p>
86+
{:else if isTeam}
87+
<p class="text u-x-small">Members: {data?.total}</p>
88+
{/if}
89+
</span>
90+
{/if}
91+
</div>
92+
{:else}
93+
Not found.
94+
{/if}
95+
</div>
1596
{/if}
1697
</div>
1798
</div>
99+
100+
<style lang="scss" global>
101+
.tippy-user .tippy-box {
102+
--p-drop-bg-color: var(--color-neutral-500);
103+
--p-drop-border-color: var(--color-neutral-200);
104+
105+
body.theme-light & {
106+
--p-drop-bg-color: var(--color-neutral-0);
107+
--p-drop-border-color: var(--color-neutral-10);
108+
}
109+
110+
inset-inline-start: -0.625rem;
111+
inset-block-end: calc(100% + 0.625rem);
112+
background-color: hsl(var(--p-drop-bg-color));
113+
border: solid 0.0625rem hsl(var(--p-drop-border-color));
114+
border-radius: var(--border-radius-small);
115+
box-shadow: var(--shadow-small);
116+
font-size: var(--font-size-0);
117+
color: hsl(var(--p-body-text-color));
118+
max-inline-size: 32.5rem;
119+
margin-inline: auto;
120+
line-height: 1.5;
121+
122+
.tippy-content {
123+
padding: 1rem;
124+
}
125+
126+
&[data-placement^='top'] > .tippy-arrow::before {
127+
border-top-color: hsl(var(--p-drop-bg-color));
128+
}
129+
&[data-placement^='bottom'] > .tippy-arrow::before {
130+
border-bottom-color: hsl(var(--p-drop-bg-color));
131+
}
132+
&[data-placement^='left'] > .tippy-arrow::before {
133+
border-left-color: hsl(var(--p-drop-bg-color));
134+
}
135+
&[data-placement^='right'] > .tippy-arrow::before {
136+
border-right-color: hsl(var(--p-drop-bg-color));
137+
}
138+
}
139+
</style>

0 commit comments

Comments
 (0)