mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
5 files changed
+246
-106
No files matched your search
@@ -0,0 +1,114 @@
|
||||
<script lang="ts" context="module">
|
||||
export type Placement = 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { createPopper, type Instance } from '@popperjs/core';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
|
||||
export let show = false;
|
||||
export let noArrow = false;
|
||||
export let placement: Placement = 'bottom-start';
|
||||
export let childStart = false;
|
||||
|
||||
let element: HTMLDivElement;
|
||||
let tooltip: HTMLDivElement;
|
||||
let arrow: HTMLDivElement;
|
||||
let instance: Instance;
|
||||
|
||||
onMount(() => {
|
||||
instance = createPopper(element, tooltip, {
|
||||
placement,
|
||||
modifiers: [
|
||||
{
|
||||
name: 'arrow',
|
||||
options: {
|
||||
element: arrow
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [0, noArrow ? 0 : 6]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'flip',
|
||||
options: {
|
||||
fallbackPlacements: ['bottom-start', 'bottom-end', 'top-start', 'top-end']
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
$: if (show) {
|
||||
instance?.update();
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
instance?.destroy();
|
||||
});
|
||||
|
||||
const onBlur = (event: MouseEvent) => {
|
||||
if (
|
||||
show &&
|
||||
!(
|
||||
event.target === element ||
|
||||
element.contains(event.target as Node) ||
|
||||
event.target === tooltip ||
|
||||
tooltip.contains(event.target as Node)
|
||||
)
|
||||
) {
|
||||
show = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<svelte:window on:click={onBlur} />
|
||||
|
||||
<div class="drop-wrapper" class:u-cross-child-start={childStart} bind:this={element}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div class="drop-tooltip" bind:this={tooltip} style="z-index: 10">
|
||||
<div class="drop-arrow" class:u-hide={!show || (show && noArrow)} bind:this={arrow} />
|
||||
{#if show}
|
||||
<slot name="list" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style global lang="scss">
|
||||
.drop-tooltip[data-popper-placement^='top'] > .drop-arrow {
|
||||
bottom: -4px;
|
||||
}
|
||||
|
||||
.drop-tooltip[data-popper-placement^='bottom'] > .drop-arrow {
|
||||
top: -4px;
|
||||
}
|
||||
|
||||
.drop-tooltip[data-popper-placement^='left'] > .drop-arrow {
|
||||
right: -4px;
|
||||
}
|
||||
|
||||
.drop-tooltip[data-popper-placement^='right'] > .drop-arrow {
|
||||
left: -4px;
|
||||
}
|
||||
.drop-arrow,
|
||||
.drop-arrow::before {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.drop-arrow::before {
|
||||
content: '';
|
||||
transform: rotate(45deg);
|
||||
background: hsl(var(--color-neutral-200));
|
||||
|
||||
body.theme-light & {
|
||||
background: hsl(var(--color-neutral-10));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,69 +1,17 @@
|
||||
<script lang="ts">
|
||||
import { createPopper, type Instance } from '@popperjs/core';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { Drop } from '.';
|
||||
import type { Placement } from './drop.svelte';
|
||||
|
||||
export let show = false;
|
||||
export let noArrow = false;
|
||||
export let placement: Placement = 'bottom-start';
|
||||
export let scrollable = false;
|
||||
export let childStart = false;
|
||||
|
||||
type Placement = 'top-start' | 'top-end' | 'bottom-start' | 'bottom-end';
|
||||
let element: HTMLDivElement;
|
||||
let tooltip: HTMLDivElement;
|
||||
let arrow: HTMLDivElement;
|
||||
let instance: Instance;
|
||||
|
||||
onMount(() => {
|
||||
instance = createPopper(element, tooltip, {
|
||||
placement,
|
||||
modifiers: [
|
||||
{
|
||||
name: 'arrow',
|
||||
options: {
|
||||
element: arrow
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [0, noArrow ? 0 : 6]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'flip',
|
||||
options: {
|
||||
fallbackPlacements: ['top-start', 'top-end', 'bottom-start', 'bottom-end']
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
$: if (show) {
|
||||
instance?.update();
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
instance?.destroy();
|
||||
});
|
||||
|
||||
const onBlur = (event: MouseEvent) => {
|
||||
if (show && !(event.target === element || element.contains(event.target as Node))) {
|
||||
show = false;
|
||||
}
|
||||
};
|
||||
export let noArrow = false;
|
||||
</script>
|
||||
|
||||
<svelte:window on:click={onBlur} />
|
||||
|
||||
<div class="drop-wrapper" class:u-cross-child-start={childStart} bind:this={element}>
|
||||
<Drop bind:show {placement} {childStart} {noArrow}>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
<div class="drop-tooltip" bind:this={tooltip} style="z-index: 10">
|
||||
<div class="drop-arrow" class:u-hide={!show} bind:this={arrow} />
|
||||
{#if show}
|
||||
<svelte:fragment slot="list">
|
||||
<div class="drop is-no-arrow" style="position: revert">
|
||||
<section
|
||||
class:u-overflow-y-auto={scrollable}
|
||||
@@ -75,40 +23,5 @@
|
||||
</section>
|
||||
<slot name="other" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style global lang="scss">
|
||||
.drop-tooltip[data-popper-placement^='top'] > .drop-arrow {
|
||||
bottom: -4px;
|
||||
}
|
||||
|
||||
.drop-tooltip[data-popper-placement^='bottom'] > .drop-arrow {
|
||||
top: -4px;
|
||||
}
|
||||
|
||||
.drop-tooltip[data-popper-placement^='left'] > .drop-arrow {
|
||||
right: -4px;
|
||||
}
|
||||
|
||||
.drop-tooltip[data-popper-placement^='right'] > .drop-arrow {
|
||||
left: -4px;
|
||||
}
|
||||
.drop-arrow,
|
||||
.drop-arrow::before {
|
||||
position: absolute;
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.drop-arrow::before {
|
||||
content: '';
|
||||
transform: rotate(45deg);
|
||||
background: hsl(var(--color-neutral-200));
|
||||
|
||||
body.theme-light & {
|
||||
background: hsl(var(--color-neutral-10));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</svelte:fragment>
|
||||
</Drop>
|
||||
@@ -14,6 +14,7 @@ export { default as List } from './list.svelte';
|
||||
export { default as ListItem } from './listItem.svelte';
|
||||
export { default as Empty } from './empty.svelte';
|
||||
export { default as EmptySearch } from './emptySearch.svelte';
|
||||
export { default as Drop } from './drop.svelte';
|
||||
export { default as DropList } from './dropList.svelte';
|
||||
export { default as DropListItem } from './dropListItem.svelte';
|
||||
export { default as DropListLink } from './dropListLink.svelte';
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
<script lang="ts">
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
import { sdkForProject } from '$lib/stores/sdk';
|
||||
import type { Models } from '@aw-labs/appwrite-console';
|
||||
import { tick } from 'svelte';
|
||||
import { AvatarInitials } from '../';
|
||||
import Output from '../output.svelte';
|
||||
|
||||
export let role: string;
|
||||
|
||||
let content: HTMLDivElement;
|
||||
let data = null;
|
||||
|
||||
async function getData(
|
||||
permission: string
|
||||
): Promise<Partial<Models.User<Record<string, unknown>> & Models.Team>> {
|
||||
const role = permission.split(':')[0];
|
||||
const id = permission.split(':')[1].split('/')[0];
|
||||
if (role === 'user') {
|
||||
const user = await sdkForProject.users.get(id);
|
||||
return user;
|
||||
}
|
||||
if (role === 'team') {
|
||||
const team = await sdkForProject.teams.get(id);
|
||||
return team;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="u-flex u-cross-center u-gap-8">
|
||||
<div class="u-flex u-cross-center u-gap-8 tippy-user">
|
||||
<div>
|
||||
{#if role === 'users'}
|
||||
<div>Users</div>
|
||||
@@ -11,7 +36,104 @@
|
||||
{:else if role === 'any'}
|
||||
<div>Any</div>
|
||||
{:else}
|
||||
<div class="u-trim-1">{role}</div>
|
||||
<div
|
||||
class="u-trim-1"
|
||||
use:tooltip={{
|
||||
interactive: true,
|
||||
allowHTML: true,
|
||||
onTrigger(instance) {
|
||||
instance.hide();
|
||||
getData(role)
|
||||
.then((n) => {
|
||||
data = n;
|
||||
})
|
||||
.finally(() => {
|
||||
tick().then(() => {
|
||||
instance.setContent(content.innerHTML);
|
||||
instance.show();
|
||||
});
|
||||
});
|
||||
}
|
||||
}}>
|
||||
{role}
|
||||
</div>
|
||||
<div class="u-hide" bind:this={content}>
|
||||
{#if data}
|
||||
{@const isUser = role.startsWith('user')}
|
||||
{@const isTeam = role.startsWith('team')}
|
||||
<div class="user-profile">
|
||||
<AvatarInitials name={data.name} size={40} />
|
||||
<span class="user-profile-info is-only-desktop">
|
||||
<span class="name">
|
||||
{data.name
|
||||
? data.name
|
||||
: data?.email
|
||||
? data?.email
|
||||
: data?.phone
|
||||
? data?.phone
|
||||
: '-'}
|
||||
</span>
|
||||
<Output value={data.$id}>{role}</Output>
|
||||
</span>
|
||||
{#if (isUser && (data?.email || data?.phone)) || isTeam}
|
||||
<span class="user-profile-sep" />
|
||||
|
||||
<span class="user-profile-empty-column" />
|
||||
<span class="user-profile-info is-only-desktop">
|
||||
{#if isUser}
|
||||
<p class="text u-x-small">{data?.email}</p>
|
||||
<p class="text u-x-small">{data?.phone}</p>
|
||||
{:else if isTeam}
|
||||
<p class="text u-x-small">Members: {data?.total}</p>
|
||||
{/if}
|
||||
</span>
|
||||
{/if}
|
||||
</div>
|
||||
{:else}
|
||||
Not found.
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style lang="scss" global>
|
||||
.tippy-user .tippy-box {
|
||||
--p-drop-bg-color: var(--color-neutral-500);
|
||||
--p-drop-border-color: var(--color-neutral-200);
|
||||
|
||||
body.theme-light & {
|
||||
--p-drop-bg-color: var(--color-neutral-0);
|
||||
--p-drop-border-color: var(--color-neutral-10);
|
||||
}
|
||||
|
||||
inset-inline-start: -0.625rem;
|
||||
inset-block-end: calc(100% + 0.625rem);
|
||||
background-color: hsl(var(--p-drop-bg-color));
|
||||
border: solid 0.0625rem hsl(var(--p-drop-border-color));
|
||||
border-radius: var(--border-radius-small);
|
||||
box-shadow: var(--shadow-small);
|
||||
font-size: var(--font-size-0);
|
||||
color: hsl(var(--p-body-text-color));
|
||||
max-inline-size: 32.5rem;
|
||||
margin-inline: auto;
|
||||
line-height: 1.5;
|
||||
|
||||
.tippy-content {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
&[data-placement^='top'] > .tippy-arrow::before {
|
||||
border-top-color: hsl(var(--p-drop-bg-color));
|
||||
}
|
||||
&[data-placement^='bottom'] > .tippy-arrow::before {
|
||||
border-bottom-color: hsl(var(--p-drop-bg-color));
|
||||
}
|
||||
&[data-placement^='left'] > .tippy-arrow::before {
|
||||
border-left-color: hsl(var(--p-drop-bg-color));
|
||||
}
|
||||
&[data-placement^='right'] > .tippy-arrow::before {
|
||||
border-right-color: hsl(var(--p-drop-bg-color));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,4 @@
|
||||
import '@testing-library/jest-dom';
|
||||
import { tick } from 'svelte';
|
||||
import { render, fireEvent } from '@testing-library/svelte';
|
||||
import { DropList } from '../../../src/lib/components';
|
||||
|
||||
@@ -29,12 +28,3 @@ test('hide drop list on wrapper click', async () => {
|
||||
await fireEvent.click(wrapper);
|
||||
expect(component.show).toStrictEqual(false);
|
||||
});
|
||||
|
||||
test('hide drop list on list click', async () => {
|
||||
const { container } = render(DropList, { ...data });
|
||||
const dropList = container.querySelector('ul.drop-list');
|
||||
|
||||
await fireEvent.click(dropList);
|
||||
await tick();
|
||||
expect(dropList).not.toBeInTheDocument();
|
||||
});
|
||||
Reference in new issue
Block a user