@hyvor/design 1.1.25-beta-org-19 → 2.0.0-beta.1
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/dist/cloud/@components/UserPicture.svelte +32 -0
- package/dist/cloud/@components/UserPicture.svelte.d.ts +8 -0
- package/dist/cloud/CloudContext/cloudContext.svelte.d.ts +2 -1
- package/dist/cloud/HyvorBar/BarUserPicture.svelte +3 -0
- package/dist/cloud/OrganizationMembers/OrganizationMembersSearch.svelte +154 -37
- package/dist/cloud/OrganizationMembers/OrganizationMembersSearch.svelte.d.ts +4 -1
- package/dist/cloud/index.d.ts +1 -0
- package/dist/cloud/index.js +1 -0
- package/dist/components/Dropdown/DropdownContent.svelte +2 -15
- package/dist/components/Dropdown/dropdownSlide.d.ts +6 -0
- package/dist/components/Dropdown/dropdownSlide.js +13 -0
- package/package.json +1 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface Props {
|
|
3
|
+
pictureUrl: string | null;
|
|
4
|
+
name: string;
|
|
5
|
+
size?: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
let { pictureUrl, name, size = 30 }: Props = $props();
|
|
9
|
+
</script>
|
|
10
|
+
|
|
11
|
+
{#if pictureUrl}
|
|
12
|
+
<img src={pictureUrl} alt={name} style="width: {size}px; height: {size}px;" />
|
|
13
|
+
{:else}
|
|
14
|
+
<span class="user-placeholder" style="width: {size}px; height: {size}px;">
|
|
15
|
+
{name ? name[0].toUpperCase() : '?'}
|
|
16
|
+
</span>
|
|
17
|
+
{/if}
|
|
18
|
+
|
|
19
|
+
<style>
|
|
20
|
+
img {
|
|
21
|
+
border-radius: 50%;
|
|
22
|
+
}
|
|
23
|
+
.user-placeholder {
|
|
24
|
+
display: inline-flex;
|
|
25
|
+
align-items: center;
|
|
26
|
+
justify-content: center;
|
|
27
|
+
border-radius: 50%;
|
|
28
|
+
color: var(--text);
|
|
29
|
+
font-size: 14px;
|
|
30
|
+
background-color: var(--input);
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
@@ -57,7 +57,7 @@ export interface CloudContext {
|
|
|
57
57
|
export interface CloudContextUser {
|
|
58
58
|
id: number;
|
|
59
59
|
name: string | null;
|
|
60
|
-
username?: string
|
|
60
|
+
username?: string;
|
|
61
61
|
email: string;
|
|
62
62
|
picture_url: string | null;
|
|
63
63
|
}
|
|
@@ -81,5 +81,6 @@ export interface OrganizationMember {
|
|
|
81
81
|
user_id: number;
|
|
82
82
|
user_username: string;
|
|
83
83
|
user_email: string;
|
|
84
|
+
user_picture_url: string | null;
|
|
84
85
|
}
|
|
85
86
|
export type OrganizationRole = 'admin' | 'manager' | 'member' | 'billing';
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import UserPicture from '../@components/UserPicture.svelte';
|
|
2
3
|
import { getCloudContext } from '../CloudContext/cloudContext.svelte.js';
|
|
3
4
|
|
|
4
5
|
const { user } = $derived(getCloudContext());
|
|
5
6
|
</script>
|
|
6
7
|
|
|
8
|
+
<UserPicture pictureUrl={user.picture_url} name={user.username || user.name || ''} size={30} />
|
|
9
|
+
|
|
7
10
|
{#if user.picture_url}
|
|
8
11
|
<img src={user.picture_url} alt={user.name} />
|
|
9
12
|
{:else}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { toast } from '../../components/index.js';
|
|
2
|
+
import { clickOutside, IconMessage, toast } from '../../components/index.js';
|
|
3
3
|
import Loader from '../../components/Loader/Loader.svelte';
|
|
4
4
|
import TextInput from '../../components/TextInput/TextInput.svelte';
|
|
5
5
|
import {
|
|
@@ -7,18 +7,30 @@
|
|
|
7
7
|
type OrganizationMember
|
|
8
8
|
} from '../CloudContext/cloudContext.svelte.js';
|
|
9
9
|
import { searchMembers } from './members.js';
|
|
10
|
+
import { dropdownSlide } from '../../components/Dropdown/dropdownSlide.js';
|
|
11
|
+
import UserPicture from '../@components/UserPicture.svelte';
|
|
12
|
+
import IconButton from '../../components/IconButton/IconButton.svelte';
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
selectedUserId?: number;
|
|
16
|
+
}
|
|
17
|
+
let { selectedUserId = $bindable(undefined) }: Props = $props();
|
|
10
18
|
|
|
11
19
|
const { instance, organization } = getCloudContext();
|
|
12
20
|
|
|
13
21
|
let search = $state('');
|
|
14
22
|
let role = $derived(organization?.role);
|
|
15
|
-
let
|
|
23
|
+
let showable = $state(false);
|
|
16
24
|
let loading = $state(false);
|
|
17
25
|
let members: undefined | OrganizationMember[] = $state(undefined);
|
|
18
26
|
|
|
27
|
+
let selectedMember = $state(null as null | OrganizationMember);
|
|
28
|
+
|
|
19
29
|
let searchTimeout: ReturnType<typeof setTimeout> | null = null;
|
|
20
30
|
|
|
21
31
|
function handleKeyUp() {
|
|
32
|
+
showable = true;
|
|
33
|
+
|
|
22
34
|
if (searchTimeout) {
|
|
23
35
|
clearTimeout(searchTimeout);
|
|
24
36
|
}
|
|
@@ -44,43 +56,99 @@
|
|
|
44
56
|
});
|
|
45
57
|
}, 500);
|
|
46
58
|
}
|
|
59
|
+
|
|
60
|
+
function handleSelect(member: OrganizationMember) {
|
|
61
|
+
selectedMember = member;
|
|
62
|
+
selectedUserId = member.user_id;
|
|
63
|
+
showable = false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function handleDeselect() {
|
|
67
|
+
selectedMember = null;
|
|
68
|
+
selectedUserId = undefined;
|
|
69
|
+
}
|
|
47
70
|
</script>
|
|
48
71
|
|
|
49
|
-
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
{#each members as member}
|
|
68
|
-
{member.user_email}
|
|
69
|
-
{/each}
|
|
72
|
+
{#if selectedMember}
|
|
73
|
+
<div class="selected-wrap">
|
|
74
|
+
<div class="selected-user">
|
|
75
|
+
<UserPicture
|
|
76
|
+
pictureUrl={selectedMember.user_picture_url}
|
|
77
|
+
name={selectedMember.user_username || selectedMember.user_email}
|
|
78
|
+
size={30}
|
|
79
|
+
/>
|
|
80
|
+
|
|
81
|
+
<div class="user-data">
|
|
82
|
+
<div class="username">
|
|
83
|
+
@{selectedMember.user_username}
|
|
84
|
+
</div>
|
|
85
|
+
<div class="email">{selectedMember.user_email}</div>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
<div>
|
|
89
|
+
<IconButton size="small" color="input" onclick={handleDeselect}>×</IconButton>
|
|
70
90
|
</div>
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
</div>
|
|
92
|
+
{:else}
|
|
93
|
+
<div
|
|
94
|
+
class="search-wrap"
|
|
95
|
+
use:clickOutside={{
|
|
96
|
+
callback: () => (showable = false)
|
|
97
|
+
}}
|
|
98
|
+
>
|
|
99
|
+
<TextInput
|
|
100
|
+
placeholder="Search member by email..."
|
|
101
|
+
bind:value={search}
|
|
102
|
+
block
|
|
103
|
+
onkeyup={handleKeyUp}
|
|
104
|
+
>
|
|
105
|
+
{#snippet end()}
|
|
106
|
+
{#if loading}
|
|
107
|
+
<Loader size={12} colorTrack="transparent" />
|
|
108
|
+
{/if}
|
|
109
|
+
{/snippet}
|
|
110
|
+
</TextInput>
|
|
111
|
+
|
|
112
|
+
{#if members !== undefined && showable}
|
|
113
|
+
<div class="search-results hds-box" transition:dropdownSlide>
|
|
114
|
+
{#if members.length}
|
|
115
|
+
{#each members as member}
|
|
116
|
+
<button class="member-row" onclick={() => handleSelect(member)}>
|
|
117
|
+
<UserPicture
|
|
118
|
+
pictureUrl={member.user_picture_url}
|
|
119
|
+
name={member.user_username || member.user_email}
|
|
120
|
+
size={30}
|
|
121
|
+
/>
|
|
122
|
+
|
|
123
|
+
<div class="user-data">
|
|
124
|
+
<div class="username">
|
|
125
|
+
@{member.user_username}
|
|
126
|
+
</div>
|
|
127
|
+
<div class="email">{member.user_email}</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div class="role">
|
|
131
|
+
{member.role}
|
|
132
|
+
</div>
|
|
133
|
+
</button>
|
|
134
|
+
{/each}
|
|
135
|
+
{:else}
|
|
136
|
+
<IconMessage empty padding={30} iconSize={35} message="No members found" />
|
|
137
|
+
{/if}
|
|
138
|
+
</div>
|
|
139
|
+
{/if}
|
|
140
|
+
</div>
|
|
141
|
+
<div class="invite-note">
|
|
142
|
+
Looking for a user outside your organization?
|
|
143
|
+
{#if role === 'admin' || role === 'manager'}
|
|
144
|
+
<a href={instance + '/account/org/members?invite'} target="_blank" class="hds-link">
|
|
145
|
+
Invite them
|
|
146
|
+
</a>
|
|
147
|
+
{:else}
|
|
148
|
+
Ask an admin to invite them
|
|
149
|
+
{/if} to your organization first.
|
|
150
|
+
</div>
|
|
151
|
+
{/if}
|
|
84
152
|
|
|
85
153
|
<style>
|
|
86
154
|
.invite-note {
|
|
@@ -95,7 +163,56 @@
|
|
|
95
163
|
position: absolute;
|
|
96
164
|
top: 100%;
|
|
97
165
|
width: 100%;
|
|
98
|
-
padding: 15px;
|
|
99
166
|
margin-top: 10px;
|
|
167
|
+
overflow: auto;
|
|
168
|
+
z-index: 10;
|
|
169
|
+
max-height: 300px;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.member-row {
|
|
173
|
+
display: flex;
|
|
174
|
+
align-items: center;
|
|
175
|
+
gap: 10px;
|
|
176
|
+
padding: 15px 25px;
|
|
177
|
+
transition: 0.1s background-color;
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
text-align: left;
|
|
180
|
+
width: 100%;
|
|
181
|
+
border-radius: 20px;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.member-row:hover {
|
|
185
|
+
background-color: var(--hover);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.username {
|
|
189
|
+
font-weight: 600;
|
|
190
|
+
}
|
|
191
|
+
.email {
|
|
192
|
+
color: var(--text-light);
|
|
193
|
+
font-size: 14px;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
.user-data {
|
|
197
|
+
flex: 1;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.role {
|
|
201
|
+
text-transform: uppercase;
|
|
202
|
+
color: var(--text-light);
|
|
203
|
+
font-size: 13px;
|
|
204
|
+
font-weight: 600;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.selected-wrap {
|
|
208
|
+
display: flex;
|
|
209
|
+
align-items: center;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.selected-user {
|
|
213
|
+
display: flex;
|
|
214
|
+
flex: 1;
|
|
215
|
+
align-items: center;
|
|
216
|
+
gap: 6px;
|
|
100
217
|
}
|
|
101
218
|
</style>
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
interface Props {
|
|
2
|
+
selectedUserId?: number;
|
|
3
|
+
}
|
|
4
|
+
declare const OrganizationMembersSearch: import("svelte").Component<Props, {}, "selectedUserId">;
|
|
2
5
|
type OrganizationMembersSearch = ReturnType<typeof OrganizationMembersSearch>;
|
|
3
6
|
export default OrganizationMembersSearch;
|
package/dist/cloud/index.d.ts
CHANGED
|
@@ -3,3 +3,4 @@ export { getCloudContext, setCloudContext, type CloudContext as CloudContextType
|
|
|
3
3
|
export { default as HyvorBar } from './HyvorBar/HyvorBar.svelte';
|
|
4
4
|
export { bar as hyvorBar } from './HyvorBar/bar.js';
|
|
5
5
|
export { default as ResourceCreator } from './ResourceCreator/ResourceCreator.svelte';
|
|
6
|
+
export { default as OrganizationMemberSearch } from './OrganizationMembers/OrganizationMembersSearch.svelte';
|
package/dist/cloud/index.js
CHANGED
|
@@ -3,3 +3,4 @@ export { getCloudContext, setCloudContext } from './CloudContext/cloudContext.sv
|
|
|
3
3
|
export { default as HyvorBar } from './HyvorBar/HyvorBar.svelte';
|
|
4
4
|
export { bar as hyvorBar } from './HyvorBar/bar.js';
|
|
5
5
|
export { default as ResourceCreator } from './ResourceCreator/ResourceCreator.svelte';
|
|
6
|
+
export { default as OrganizationMemberSearch } from './OrganizationMembers/OrganizationMembersSearch.svelte';
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
import { onMount } from 'svelte';
|
|
3
3
|
import { clickOutside } from '../directives/clickOutside.js';
|
|
4
4
|
import debounce from '../directives/debounce.js';
|
|
5
|
-
import { elasticInOut } from 'svelte/easing';
|
|
6
5
|
import type { DropdownAlign, DropdownPosition } from './dropdown.types.js';
|
|
6
|
+
import { dropdownSlide } from './dropdownSlide.js';
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
show: boolean;
|
|
@@ -116,19 +116,6 @@
|
|
|
116
116
|
mutationObserver.disconnect();
|
|
117
117
|
};
|
|
118
118
|
});
|
|
119
|
-
|
|
120
|
-
function slideIn(node: any) {
|
|
121
|
-
return {
|
|
122
|
-
duration: 100,
|
|
123
|
-
easing: elasticInOut,
|
|
124
|
-
css: (t: number) => {
|
|
125
|
-
return `
|
|
126
|
-
opacity: ${0.2 + t * 0.8};
|
|
127
|
-
transform: translateY(-${(1 - t) * 5}px) scale(${0.95 + t * 0.05});
|
|
128
|
-
`;
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
119
|
</script>
|
|
133
120
|
|
|
134
121
|
<svelte:window onresize={debouncedPosition} onscroll={debouncedPosition} />
|
|
@@ -141,7 +128,7 @@
|
|
|
141
128
|
}}
|
|
142
129
|
bind:this={contentWrap}
|
|
143
130
|
style="width: {width}px"
|
|
144
|
-
transition:
|
|
131
|
+
transition:dropdownSlide
|
|
145
132
|
>
|
|
146
133
|
<div class="hds-box content" style:padding="{padding}px">
|
|
147
134
|
{@render children?.()}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { elasticInOut } from 'svelte/easing';
|
|
2
|
+
export function dropdownSlide(node) {
|
|
3
|
+
return {
|
|
4
|
+
duration: 100,
|
|
5
|
+
easing: elasticInOut,
|
|
6
|
+
css: (t) => {
|
|
7
|
+
return `
|
|
8
|
+
opacity: ${0.2 + t * 0.8};
|
|
9
|
+
transform: translateY(-${(1 - t) * 5}px) scale(${0.95 + t * 0.05});
|
|
10
|
+
`;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
}
|
package/package.json
CHANGED