@axium/client 0.14.5 → 0.15.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/assets/styles.css CHANGED
@@ -2,9 +2,6 @@
2
2
 
3
3
  * {
4
4
  box-sizing: border-box;
5
- color: hsl(0 0 var(--fg-light));
6
- accent-color: hsl(0 0 var(--fg-light));
7
- font-family: sans-serif;
8
5
  }
9
6
 
10
7
  body {
@@ -16,6 +13,11 @@ body {
16
13
  overflow-y: scroll;
17
14
  }
18
15
 
16
+ a {
17
+ text-decoration: none;
18
+ color: inherit;
19
+ }
20
+
19
21
  .main {
20
22
  padding: 2em;
21
23
  border-radius: 1em;
@@ -57,9 +59,18 @@ select {
57
59
  padding: 0.5em 1em;
58
60
  }
59
61
 
62
+ button.reset {
63
+ border-radius: unset;
64
+ border: none;
65
+ background-color: unset;
66
+ padding: unset;
67
+ }
68
+
60
69
  textarea {
61
70
  background: var(--bg-normal);
62
71
  border: 1px solid var(--border-accent);
72
+ font-family: inherit;
73
+ font-size: inherit;
63
74
  }
64
75
 
65
76
  input,
@@ -219,19 +230,19 @@ input.error {
219
230
  display: flex;
220
231
  flex-direction: column;
221
232
  gap: 0.1em;
222
- }
223
233
 
224
- [popover] .menu-item {
225
- display: inline-flex;
226
- align-items: center;
227
- padding: 0.5em 0.75em;
228
- gap: 1em;
229
- border-radius: 0.5em;
230
- user-select: none;
234
+ .menu-item {
235
+ display: inline-flex;
236
+ align-items: center;
237
+ padding: 0.5em 0.75em;
238
+ gap: 1em;
239
+ border-radius: 0.5em;
240
+ user-select: none;
231
241
 
232
- &:hover {
233
- background-color: var(--bg-strong);
234
- cursor: pointer;
242
+ &:hover {
243
+ background-color: var(--bg-strong);
244
+ cursor: pointer;
245
+ }
235
246
  }
236
247
  }
237
248
 
package/lib/Icon.svelte CHANGED
@@ -4,7 +4,7 @@
4
4
  const href = $derived(`/icons/${style}.svg#${id}`);
5
5
  </script>
6
6
 
7
- <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" {...rest}>
7
+ <svg class="Icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" width="1em" height="1em" {...rest}>
8
8
  <use {href} />
9
9
  </svg>
10
10
 
@@ -11,11 +11,11 @@
11
11
  }
12
12
  </script>
13
13
 
14
- <div {onclick}>
14
+ <div class="Popover" {onclick} style:display="contents">
15
15
  {#if toggle}
16
16
  {@render toggle()}
17
17
  {:else}
18
- <span class={['popover-toggle', showToggle == 'hover' && 'toggle-hover']}>
18
+ <span class={[showToggle == 'hover' && 'toggle-hover']}>
19
19
  <Icon i="ellipsis" />
20
20
  </span>
21
21
  {/if}
@@ -26,12 +26,17 @@
26
26
  </div>
27
27
 
28
28
  <style>
29
- .popover-toggle:hover {
30
- cursor: pointer;
31
- }
29
+ div.Popover {
30
+ anchor-scope: --popover;
31
+
32
+ > :global(:first-child:hover) {
33
+ cursor: pointer;
34
+ }
32
35
 
33
- .popover-toggle {
34
- user-select: none;
36
+ > :global(:first-child) {
37
+ user-select: none;
38
+ anchor-name: --popover;
39
+ }
35
40
  }
36
41
 
37
42
  @media (width > 700px) {
@@ -44,7 +49,8 @@
44
49
  }
45
50
  }
46
51
 
47
- .popover-toggle + [popover] {
52
+ :popover-open {
53
+ position-anchor: --popover;
48
54
  position-try-fallbacks:
49
55
  flip-inline,
50
56
  flip-block,
@@ -24,7 +24,7 @@
24
24
  let value = $state<string>();
25
25
  let gotError = $state<boolean>(false);
26
26
 
27
- async function onchange() {
27
+ async function oninput() {
28
28
  if (!value || !value.length) {
29
29
  results = [];
30
30
  return;
@@ -55,7 +55,7 @@
55
55
  }
56
56
  </script>
57
57
 
58
- <input bind:value type="text" placeholder="Add users and roles" {onchange} onkeyup={onchange} />
58
+ <input bind:value type="text" placeholder="Add users and roles" {oninput} />
59
59
  {#if !gotError && value}
60
60
  <!-- Don't show results when we can't use the discovery API -->
61
61
  <div class="results">
@@ -1,66 +1,78 @@
1
1
  <script lang="ts">
2
- import type { User } from '@axium/core/user';
3
- import { getUserImage } from '@axium/core';
4
2
  import { fetchAPI } from '@axium/client/requests';
3
+ import { getUserImage } from '@axium/core';
4
+ import type { UserPublic } from '@axium/core/user';
5
5
  import Icon from './Icon.svelte';
6
- import Popover from './Popover.svelte';
7
6
  import Logout from './Logout.svelte';
7
+ import Popover from './Popover.svelte';
8
8
 
9
- const { user }: { user: Partial<User> } = $props();
9
+ const { user }: { user?: UserPublic } = $props();
10
10
  </script>
11
11
 
12
- <Popover>
13
- {#snippet toggle()}
14
- <div style:display="contents">
15
- <img src={getUserImage(user)} alt={user.name} />
16
- {user.name}
17
- </div>
18
- {/snippet}
19
-
20
- <a class="menu-item" href="/account">
21
- <Icon i="user" --size="1.5em" />
22
- <span>Your Account</span>
23
- </a>
12
+ {#if user}
13
+ <Popover>
14
+ {#snippet toggle()}
15
+ <div class="UserMenu toggle">
16
+ <img src={getUserImage(user)} alt={user.name} />
17
+ {user.name}
18
+ </div>
19
+ {/snippet}
24
20
 
25
- {#if user.isAdmin}
26
- <a class="menu-item" href="/admin">
27
- <Icon i="gear-complex" --size="1.5em" />
28
- <span>Administration</span>
21
+ <a class="menu-item" href="/account">
22
+ <Icon i="user" --size="1.5em" />
23
+ <span>Your Account</span>
29
24
  </a>
30
- {/if}
31
25
 
32
- {#await fetchAPI('GET', 'apps')}
33
- <i>Loading...</i>
34
- {:then apps}
35
- {#each apps as app}
36
- <a class="menu-item" href="/{app.id}">
37
- {#if app.image}
38
- <img src={app.image} alt={app.name} width="1em" height="1em" />
39
- {:else if app.icon}
40
- <Icon i={app.icon} --size="1.5em" />
41
- {:else}
42
- <Icon i="image-circle-xmark" --size="1.5em" />
43
- {/if}
44
- <span>{app.name}</span>
26
+ {#if user.isAdmin}
27
+ <a class="menu-item" href="/admin">
28
+ <Icon i="gear-complex" --size="1.5em" />
29
+ <span>Administration</span>
45
30
  </a>
46
- {:else}
47
- <i>No apps available.</i>
48
- {/each}
49
- {:catch}
50
- <i>Couldn't load apps.</i>
51
- {/await}
31
+ {/if}
52
32
 
53
- <button style:display="contents" command="show-modal" commandfor="logout">
54
- <span class="menu-item logout">
33
+ {#await fetchAPI('GET', 'apps')}
34
+ <i>Loading...</i>
35
+ {:then apps}
36
+ {#each apps as app}
37
+ <a class="menu-item" href="/{app.id}">
38
+ {#if app.image}
39
+ <img src={app.image} alt={app.name} width="1em" height="1em" />
40
+ {:else if app.icon}
41
+ <Icon i={app.icon} --size="1.5em" />
42
+ {:else}
43
+ <Icon i="image-circle-xmark" --size="1.5em" />
44
+ {/if}
45
+ <span>{app.name}</span>
46
+ </a>
47
+ {:else}
48
+ <i>No apps available.</i>
49
+ {/each}
50
+ {:catch}
51
+ <i>Couldn't load apps.</i>
52
+ {/await}
53
+
54
+ <button class="menu-item logout reset" command="show-modal" commandfor="logout">
55
55
  <Icon i="right-from-bracket" --size="1.5em" --fill="hsl(0 33 var(--fg-light))" />
56
56
  <span>Logout</span>
57
- </span>
58
- </button>
59
- </Popover>
57
+ </button>
58
+ </Popover>
60
59
 
61
- <Logout />
60
+ <Logout />
61
+ {:else}
62
+ <div class="UserMenu login">
63
+ <a href="/login?after={location.pathname}">Login</a>
64
+ </div>
65
+ {/if}
62
66
 
63
67
  <style>
68
+ .UserMenu {
69
+ border-radius: 0.5em;
70
+ padding: 0.5em;
71
+ border: 1px solid var(--border-accent);
72
+ cursor: pointer;
73
+ background-color: var(--bg-alt);
74
+ }
75
+
64
76
  img {
65
77
  width: 2em;
66
78
  height: 2em;
@@ -69,7 +81,18 @@
69
81
  margin-right: 0.5em;
70
82
  }
71
83
 
72
- span.logout > span {
84
+ .logout {
73
85
  color: hsl(0 33 var(--fg-light));
86
+ font-size: 16px;
87
+ }
88
+
89
+ :global(.UserMenu + div:popover-open) {
90
+ position: fixed;
91
+ left: unset;
92
+ right: anchor(right);
93
+ top: calc(anchor(bottom) + 0.5em);
94
+ width: fit-content;
95
+ height: fit-content;
96
+ cursor: default;
74
97
  }
75
98
  </style>
@@ -87,14 +87,14 @@
87
87
  updateValue(rootValue);
88
88
  }
89
89
 
90
- const onkeyup = onchange;
90
+ const oninput = onchange;
91
91
  </script>
92
92
 
93
93
  {#snippet _in(rest: HTMLInputAttributes)}
94
94
  <div class="ZodInput">
95
95
  {#if !noLabel}<label for={id}>{label || path}</label>{/if}
96
96
  {#if error}<span class="ZodInput-error error-text">{error}</span>{/if}
97
- <input {id} {...rest} bind:value {onchange} {onkeyup} required={!optional} {defaultValue} class={[error && 'error']} />
97
+ <input {id} {...rest} bind:value {onchange} {oninput} required={!optional} {defaultValue} class={[error && 'error']} />
98
98
  </div>
99
99
  {/snippet}
100
100
 
@@ -107,7 +107,7 @@
107
107
  {:else if schema.type == 'boolean'}
108
108
  <div class="ZodInput">
109
109
  {#if !noLabel}<label for="{id}:checkbox">{label || path}</label>{/if}
110
- <input bind:checked={value} id="{id}:checkbox" type="checkbox" {onchange} {onkeyup} required={!optional} />
110
+ <input bind:checked={value} id="{id}:checkbox" type="checkbox" {onchange} required={!optional} />
111
111
  <label for="{id}:checkbox" {id} class="checkbox">
112
112
  {#if value}<Icon i="check" --size="1.3em" />{/if}
113
113
  </label>
@@ -123,7 +123,7 @@
123
123
  {:else if schema.type == 'literal'}
124
124
  <div class="ZodInput">
125
125
  <label for={id}>{label || path}</label>
126
- <select bind:value {id} {onchange} {onkeyup} required={!optional}>
126
+ <select bind:value {id} {oninput} required={!optional}>
127
127
  {#each schema.values as value}
128
128
  <option {value} selected={value === value}>{value}</option>
129
129
  {/each}
@@ -143,15 +143,7 @@
143
143
  <div class="ZodInput-array">
144
144
  {#each value, i}
145
145
  <div class="ZodInput-element">
146
- <input
147
- id="{id}.{i}"
148
- bind:value={value[i]}
149
- {onchange}
150
- {onkeyup}
151
- required={!optional}
152
- {defaultValue}
153
- class={[error && 'error']}
154
- />
146
+ <input id="{id}.{i}" bind:value={value[i]} {oninput} required={!optional} {defaultValue} class={[error && 'error']} />
155
147
  <button
156
148
  onclick={e => {
157
149
  value.splice(i, 1);
@@ -192,7 +184,7 @@
192
184
  <div class="ZodInput">
193
185
  {#if !noLabel}<label for={id}>{label || path}</label>{/if}
194
186
  {#if error}<span class="ZodInput-error error-text">{error}</span>{/if}
195
- <select {id} {onchange} {onkeyup} bind:value required={!optional}>
187
+ <select {id} {onchange} bind:value required={!optional}>
196
188
  {#each Object.entries(schema.enum) as [key, value]}
197
189
  <option {value} selected={value === value}>{key}</option>
198
190
  {/each}
package/lib/index.ts CHANGED
@@ -14,6 +14,7 @@ export { default as Toast } from './Toast.svelte';
14
14
  export { default as Upload } from './Upload.svelte';
15
15
  export { default as URLText } from './URLText.svelte';
16
16
  export { default as UserCard } from './UserCard.svelte';
17
+ export { default as UserDiscovery } from './UserDiscovery.svelte';
17
18
  export { default as UserMenu } from './UserMenu.svelte';
18
19
  export { default as Version } from './Version.svelte';
19
20
  export { default as ZodForm } from './ZodForm.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axium/client",
3
- "version": "0.14.5",
3
+ "version": "0.15.1",
4
4
  "author": "James Prevett <jp@jamespre.dev>",
5
5
  "funding": {
6
6
  "type": "individual",
@@ -42,7 +42,7 @@
42
42
  },
43
43
  "peerDependencies": {
44
44
  "@axium/core": ">=0.19.0",
45
- "utilium": "^2.3.8",
45
+ "utilium": "^2.6.3",
46
46
  "zod": "^4.0.5",
47
47
  "svelte": "^5.36.0"
48
48
  },