@immich/ui 0.6.0 → 0.8.0

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.
Files changed (64) hide show
  1. package/README.md +39 -45
  2. package/dist/assets/appstore-badge.svg +46 -0
  3. package/dist/assets/fdroid-badge.svg +124 -0
  4. package/dist/assets/playstore-badge.png +0 -0
  5. package/dist/components/Alert/Alert.svelte +13 -7
  6. package/dist/components/Alert/Alert.svelte.d.ts +3 -2
  7. package/dist/components/AppShell/AppShell.svelte +34 -0
  8. package/dist/components/AppShell/AppShell.svelte.d.ts +6 -0
  9. package/dist/components/AppShell/AppShellHeader.svelte +15 -0
  10. package/dist/components/AppShell/AppShellHeader.svelte.d.ts +5 -0
  11. package/dist/components/AppShell/AppShellSidebar.svelte +23 -0
  12. package/dist/components/AppShell/AppShellSidebar.svelte.d.ts +7 -0
  13. package/dist/components/AppShell/PageLayout.svelte +44 -0
  14. package/dist/components/AppShell/PageLayout.svelte.d.ts +9 -0
  15. package/dist/components/Avatar/Avatar.svelte +66 -0
  16. package/dist/components/Avatar/Avatar.svelte.d.ts +7 -0
  17. package/dist/components/Card/Card.svelte +7 -9
  18. package/dist/components/Card/CardBody.svelte +1 -1
  19. package/dist/components/Card/CardFooter.svelte +6 -2
  20. package/dist/components/Card/CardFooter.svelte.d.ts +1 -0
  21. package/dist/components/Code/Code.svelte +62 -0
  22. package/dist/components/Code/Code.svelte.d.ts +9 -0
  23. package/dist/components/Form/Checkbox.svelte +67 -27
  24. package/dist/components/Form/Checkbox.svelte.d.ts +2 -2
  25. package/dist/components/Form/Input.svelte +43 -35
  26. package/dist/components/Form/Input.svelte.d.ts +2 -9
  27. package/dist/components/Form/PasswordInput.svelte +31 -0
  28. package/dist/components/Form/PasswordInput.svelte.d.ts +3 -0
  29. package/dist/components/FormatBytes/FormatBytes.svelte +16 -0
  30. package/dist/components/FormatBytes/FormatBytes.svelte.d.ts +6 -0
  31. package/dist/components/Heading/Heading.svelte +2 -1
  32. package/dist/components/Heading/Heading.svelte.d.ts +1 -1
  33. package/dist/components/LoadingSpinner/LoadingSpinner.svelte +54 -0
  34. package/dist/components/LoadingSpinner/LoadingSpinner.svelte.d.ts +7 -0
  35. package/dist/components/Logo/Logo.svelte +8 -8
  36. package/dist/components/Logo/Logo.svelte.d.ts +1 -2
  37. package/dist/components/MultiSelect/MultiSelect.svelte +15 -0
  38. package/dist/components/MultiSelect/MultiSelect.svelte.d.ts +3 -0
  39. package/dist/components/Navbar/NavbarGroup.svelte +12 -0
  40. package/dist/components/Navbar/NavbarGroup.svelte.d.ts +4 -0
  41. package/dist/components/Navbar/NavbarItem.svelte +30 -0
  42. package/dist/components/Navbar/NavbarItem.svelte.d.ts +7 -0
  43. package/dist/components/Scrollable/Scrollable.svelte +41 -0
  44. package/dist/components/Scrollable/Scrollable.svelte.d.ts +6 -0
  45. package/dist/components/Select/Select.svelte +15 -0
  46. package/dist/components/Select/Select.svelte.d.ts +3 -0
  47. package/dist/components/Switch/Switch.svelte +99 -0
  48. package/dist/components/Switch/Switch.svelte.d.ts +10 -0
  49. package/dist/components/Text/Text.svelte +15 -3
  50. package/dist/components/Text/Text.svelte.d.ts +1 -1
  51. package/dist/constants.d.ts +3 -0
  52. package/dist/constants.js +3 -0
  53. package/dist/index.d.ts +25 -0
  54. package/dist/index.js +28 -0
  55. package/dist/internal/Button.svelte +28 -16
  56. package/dist/internal/Select.svelte +174 -0
  57. package/dist/internal/Select.svelte.d.ts +9 -0
  58. package/dist/services/theme.svelte.d.ts +5 -0
  59. package/dist/services/theme.svelte.js +13 -0
  60. package/dist/types.d.ts +45 -2
  61. package/dist/types.js +5 -1
  62. package/dist/utilities/byte-units.d.ts +52 -0
  63. package/dist/utilities/byte-units.js +75 -0
  64. package/package.json +7 -5
@@ -1,15 +1,19 @@
1
1
  <script lang="ts">
2
2
  import { ChildKey } from '../../constants.js';
3
3
  import Child from '../../internal/Child.svelte';
4
+ import { cleanClass } from '../../utils.js';
4
5
  import type { Snippet } from 'svelte';
5
6
 
6
7
  type Props = {
7
8
  children: Snippet;
9
+ class?: string;
8
10
  };
9
11
 
10
- let { children }: Props = $props();
12
+ let { children, class: className }: Props = $props();
11
13
  </script>
12
14
 
13
15
  <Child for={ChildKey.Card} as={ChildKey.CardFooter}>
14
- {@render children?.()}
16
+ <div class={cleanClass('flex items-center p-4 pt-0', className)}>
17
+ {@render children?.()}
18
+ </div>
15
19
  </Child>
@@ -1,5 +1,6 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  declare const CardFooter: import("svelte").Component<{
3
3
  children: Snippet;
4
+ class?: string;
4
5
  }, {}, "">;
5
6
  export default CardFooter;
@@ -0,0 +1,62 @@
1
+ <script lang="ts">
2
+ import type { Color, Size } from '../../types.js';
3
+ import type { Snippet } from 'svelte';
4
+ import { tv } from 'tailwind-variants';
5
+
6
+ type Props = {
7
+ color?: Color | 'muted';
8
+ size?: Size;
9
+ variant?: 'filled';
10
+ children: Snippet;
11
+ };
12
+
13
+ const { size = 'medium', variant, color = 'secondary', children }: Props = $props();
14
+
15
+ const styles = tv({
16
+ base: 'font-monospace',
17
+ variants: {
18
+ textColor: {
19
+ muted: 'text-gray-600 dark:text-gray-400',
20
+ primary: 'text-primary',
21
+ secondary: 'text-dark',
22
+ success: 'text-success',
23
+ danger: 'text-danger',
24
+ warning: 'text-warning',
25
+ info: 'text-info',
26
+ },
27
+
28
+ filled: {
29
+ true: 'rounded px-2 py-1',
30
+ false: '',
31
+ },
32
+
33
+ filledColor: {
34
+ false: '',
35
+ muted: 'bg-gray-600 dark:bg-gray-800 text-light',
36
+ primary: 'bg-primary text-light',
37
+ secondary: 'bg-dark text-light',
38
+ success: 'bg-success text-light',
39
+ danger: 'bg-danger text-light',
40
+ warning: 'bg-warning text-light',
41
+ info: 'bg-info text-dark',
42
+ },
43
+
44
+ size: {
45
+ tiny: 'text-xs',
46
+ small: 'text-sm',
47
+ medium: 'text-md',
48
+ large: 'text-lg',
49
+ giant: 'text-xl',
50
+ },
51
+ },
52
+ });
53
+ </script>
54
+
55
+ <code
56
+ class={styles({
57
+ filled: variant === 'filled',
58
+ filledColor: variant === 'filled' && color,
59
+ textColor: color,
60
+ size,
61
+ })}>{@render children()}</code
62
+ >
@@ -0,0 +1,9 @@
1
+ import type { Color, Size } from '../../types.js';
2
+ import type { Snippet } from 'svelte';
3
+ declare const Code: import("svelte").Component<{
4
+ color?: Color | "muted";
5
+ size?: Size;
6
+ variant?: "filled";
7
+ children: Snippet;
8
+ }, {}, "">;
9
+ export default Code;
@@ -1,10 +1,11 @@
1
1
  <script lang="ts">
2
- import { Checkbox as CheckboxPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
3
- import { mdiCheck, mdiMinus } from '@mdi/js';
2
+ import { getFieldContext } from '../../common/context.svelte.js';
4
3
  import Icon from '../Icon/Icon.svelte';
5
- import { tv } from 'tailwind-variants';
6
4
  import type { Color, Shape, Size } from '../../types.js';
7
- import { cleanClass } from '../../utils.js';
5
+ import { cleanClass, generateId } from '../../utils.js';
6
+ import { mdiCheck, mdiMinus } from '@mdi/js';
7
+ import { Checkbox as CheckboxPrimitive, type WithoutChildrenOrChild } from 'bits-ui';
8
+ import { tv } from 'tailwind-variants';
8
9
 
9
10
  type CheckboxProps = WithoutChildrenOrChild<CheckboxPrimitive.RootProps> & {
10
11
  color?: Color;
@@ -22,8 +23,16 @@
22
23
  ...restProps
23
24
  }: CheckboxProps = $props();
24
25
 
25
- const container = tv({
26
- base: 'ring-offset-background focus-visible:ring-ring peer box-content border focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[state=checked]:bg-primary data-[disabled=true]:opacity-50 overflow-hidden',
26
+ const {
27
+ label,
28
+ readOnly = false,
29
+ required = false,
30
+ invalid = false,
31
+ disabled = false,
32
+ } = $derived(getFieldContext());
33
+
34
+ const containerStyles = tv({
35
+ base: 'border-2 ring-offset-background focus-visible:ring-ring peer box-content focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 data-[disabled=true]:cursor-not-allowed data-[state=checked]:bg-primary data-[disabled=true]:opacity-50 overflow-hidden',
27
36
  variants: {
28
37
  shape: {
29
38
  rectangle: 'rounded-none',
@@ -32,7 +41,7 @@
32
41
  },
33
42
  color: {
34
43
  primary: 'border-primary',
35
- secondary: 'border-secondary',
44
+ secondary: 'border-dark',
36
45
  success: 'border-success',
37
46
  danger: 'border-danger',
38
47
  warning: 'border-warning',
@@ -55,7 +64,7 @@
55
64
  },
56
65
  });
57
66
 
58
- const icon = tv({
67
+ const iconStyles = tv({
59
68
  variants: {
60
69
  fullWidth: {
61
70
  true: 'w-full',
@@ -70,24 +79,55 @@
70
79
  },
71
80
  },
72
81
  });
82
+
83
+ const labelStyles = tv({
84
+ base: '',
85
+ variants: {
86
+ size: {
87
+ tiny: 'text-xs',
88
+ small: 'text-sm',
89
+ medium: 'text-md',
90
+ large: 'text-lg',
91
+ giant: 'text-xl',
92
+ },
93
+ },
94
+ });
95
+
96
+ const id = generateId();
97
+ const inputId = `input-${id}`;
98
+ const labelId = `label-${id}`;
73
99
  </script>
74
100
 
75
- <CheckboxPrimitive.Root
76
- bind:ref
77
- class={cleanClass(
78
- container({ size, color, shape, roundedSize: shape === 'semi-round' ? size : undefined }),
79
- className,
80
- )}
81
- bind:checked
82
- {...restProps}
83
- >
84
- {#snippet children({ checked })}
85
- <div class={cleanClass('flex items-center justify-center text-current')}>
86
- {#if checked === true}
87
- <Icon icon={mdiCheck} size="100%" class={cleanClass(icon({ color }))} />
88
- {:else if checked === 'indeterminate'}
89
- <Icon icon={mdiMinus} size="100%" class={cleanClass(icon({ color }))} />
90
- {/if}
91
- </div>
92
- {/snippet}
93
- </CheckboxPrimitive.Root>
101
+ <div class="flex flex-col gap-1">
102
+ {#if label}
103
+ <label id={labelId} for={inputId} class={labelStyles({ size })}>{label}</label>
104
+ {/if}
105
+
106
+ <CheckboxPrimitive.Root
107
+ bind:ref
108
+ class={cleanClass(
109
+ containerStyles({
110
+ size,
111
+ color: invalid ? 'danger' : color,
112
+ shape,
113
+ roundedSize: shape === 'semi-round' ? size : undefined,
114
+ }),
115
+ className,
116
+ )}
117
+ bind:checked
118
+ disabled={disabled || readOnly}
119
+ {required}
120
+ aria-readonly={disabled || readOnly}
121
+ {...restProps}
122
+ >
123
+ {#snippet children({ checked, indeterminate })}
124
+ <div class={cleanClass('flex items-center justify-center text-current')}>
125
+ {#if indeterminate}
126
+ <Icon icon={mdiMinus} size="100%" class={cleanClass(iconStyles({ color }))} />
127
+ {:else if checked}
128
+ <Icon icon={mdiCheck} size="100%" class={cleanClass(iconStyles({ color }))} />
129
+ {/if}
130
+ </div>
131
+ {/snippet}
132
+ </CheckboxPrimitive.Root>
133
+ </div>
@@ -1,8 +1,8 @@
1
- import { Checkbox as CheckboxPrimitive } from 'bits-ui';
2
1
  import type { Color, Shape, Size } from '../../types.js';
2
+ import { Checkbox as CheckboxPrimitive } from 'bits-ui';
3
3
  declare const Checkbox: import("svelte").Component<Omit<Omit<CheckboxPrimitive.RootProps, "child">, "children"> & {
4
4
  color?: Color;
5
5
  shape?: Shape;
6
6
  size?: Size;
7
- }, {}, "ref" | "checked">;
7
+ }, {}, "checked" | "ref">;
8
8
  export default Checkbox;
@@ -1,25 +1,18 @@
1
1
  <script lang="ts">
2
2
  import { getFieldContext } from '../../common/context.svelte.js';
3
- import type { Shape, Size } from '../../types.js';
3
+ import type { InputProps } from '../../types.js';
4
4
  import { cleanClass, generateId } from '../../utils.js';
5
- import type { HTMLInputAttributes } from 'svelte/elements';
6
5
  import { tv } from 'tailwind-variants';
7
6
 
8
- type Props = {
9
- class?: string;
10
- value?: string;
11
- size?: Size;
12
- shape?: Shape;
13
- inputSize?: HTMLInputAttributes['size'];
14
- } & Omit<HTMLInputAttributes, 'size'>;
15
-
16
7
  let {
8
+ containerRef = $bindable(null),
17
9
  shape = 'semi-round',
18
10
  size = 'medium',
19
11
  class: className,
20
12
  value = $bindable<string>(),
13
+ trailingIcon,
21
14
  ...restProps
22
- }: Props = $props();
15
+ }: InputProps = $props();
23
16
 
24
17
  const {
25
18
  label,
@@ -43,7 +36,7 @@
43
36
  });
44
37
 
45
38
  const inputStyles = tv({
46
- base: 'outline-none disabled:cursor-not-allowed bg-gray-200 dark:bg-gray-600 disabled:bg-gray-300 disabled:text-gray-200 dark:disabled:bg-gray-800 aria-readonly:text-dark/50 dark:aria-readonly:text-dark/75',
39
+ base: 'w-full outline-none disabled:cursor-not-allowed bg-gray-200 dark:bg-gray-600 disabled:bg-gray-300 disabled:text-gray-200 dark:disabled:bg-gray-800 aria-readonly:text-dark/50 dark:aria-readonly:text-dark/75',
47
40
  variants: {
48
41
  shape: {
49
42
  rectangle: 'rounded-none',
@@ -80,30 +73,45 @@
80
73
  const labelId = `label-${id}`;
81
74
  </script>
82
75
 
83
- <div class="flex flex-col gap-1">
76
+ <div class="flex flex-col gap-1" bind:this={containerRef}>
84
77
  {#if label}
85
78
  <label id={labelId} for={inputId} class={labelStyles({ size })}>{label}</label>
86
79
  {/if}
87
- <input
88
- id={label && inputId}
89
- aria-labelledby={label && labelId}
90
- {required}
91
- aria-required={required}
92
- {disabled}
93
- aria-disabled={disabled}
94
- readonly={readOnly}
95
- aria-readonly={readOnly}
96
- class={cleanClass(
97
- inputStyles({
98
- shape,
99
- textSize: size,
100
- padding: shape === 'round' ? 'round' : 'base',
101
- roundedSize: shape === 'semi-round' ? size : undefined,
102
- invalid,
103
- }),
104
- className,
105
- )}
106
- bind:value
107
- {...restProps}
108
- />
80
+
81
+ <div class="relative">
82
+ <input
83
+ id={label && inputId}
84
+ aria-labelledby={label && labelId}
85
+ {required}
86
+ aria-required={required}
87
+ {disabled}
88
+ aria-disabled={disabled}
89
+ readonly={readOnly}
90
+ aria-readonly={readOnly}
91
+ class={cleanClass(
92
+ inputStyles({
93
+ shape,
94
+ textSize: size,
95
+ padding: shape === 'round' ? 'round' : 'base',
96
+ roundedSize: shape === 'semi-round' ? size : undefined,
97
+ invalid,
98
+ }),
99
+ trailingIcon && '!pr-10',
100
+ className,
101
+ )}
102
+ bind:value
103
+ {...restProps}
104
+ />
105
+ {#if trailingIcon}
106
+ <div tabindex="-1" class={cleanClass('absolute inset-y-0 end-0 flex items-center')}>
107
+ {@render trailingIcon()}
108
+ </div>
109
+ {/if}
110
+ </div>
109
111
  </div>
112
+
113
+ <style>
114
+ input::-ms-reveal {
115
+ display: none;
116
+ }
117
+ </style>
@@ -1,10 +1,3 @@
1
- import type { Shape, Size } from '../../types.js';
2
- import type { HTMLInputAttributes } from 'svelte/elements';
3
- declare const Input: import("svelte").Component<{
4
- class?: string;
5
- value?: string;
6
- size?: Size;
7
- shape?: Shape;
8
- inputSize?: HTMLInputAttributes["size"];
9
- } & Omit<HTMLInputAttributes, "size">, {}, "value">;
1
+ import type { InputProps } from '../../types.js';
2
+ declare const Input: import("svelte").Component<InputProps, {}, "value" | "containerRef">;
10
3
  export default Input;
@@ -0,0 +1,31 @@
1
+ <script lang="ts">
2
+ import Input from './Input.svelte';
3
+ import IconButton from '../IconButton/IconButton.svelte';
4
+ import type { PasswordInputProps } from '../../types.js';
5
+ import { mdiEyeOffOutline, mdiEyeOutline } from '@mdi/js';
6
+
7
+ let {
8
+ value = $bindable<string>(),
9
+ showLabel = 'Show password',
10
+ hideLabel = 'Hide password',
11
+ isVisible = $bindable<boolean>(false),
12
+ color = 'secondary',
13
+ ...props
14
+ }: PasswordInputProps = $props();
15
+ </script>
16
+
17
+ <Input bind:value type={isVisible ? 'text' : 'password'} {color} {...props}>
18
+ {#snippet trailingIcon()}
19
+ {#if value?.length > 0}
20
+ <IconButton
21
+ variant="ghost"
22
+ shape="round"
23
+ color="secondary"
24
+ class="m-1"
25
+ icon={isVisible ? mdiEyeOffOutline : mdiEyeOutline}
26
+ onclick={() => (isVisible = !isVisible)}
27
+ title={isVisible ? hideLabel : showLabel}
28
+ ></IconButton>
29
+ {/if}
30
+ {/snippet}
31
+ </Input>
@@ -0,0 +1,3 @@
1
+ import type { PasswordInputProps } from '../../types.js';
2
+ declare const PasswordInput: import("svelte").Component<PasswordInputProps, {}, "value" | "isVisible">;
3
+ export default PasswordInput;
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import { getBytesWithUnit } from '../../utilities/byte-units.js';
3
+
4
+ type Props = {
5
+ bytes: number;
6
+ precision?: number;
7
+ variant?: 'short' | 'narrow';
8
+ };
9
+
10
+ const { bytes, precision, variant = 'short' }: Props = $props();
11
+
12
+ const [value, unit] = $derived(getBytesWithUnit(bytes, precision));
13
+ const separator = $derived(variant === 'narrow' ? '' : ' ');
14
+ </script>
15
+
16
+ <span>{value}{separator}{unit}</span>
@@ -0,0 +1,6 @@
1
+ declare const FormatBytes: import("svelte").Component<{
2
+ bytes: number;
3
+ precision?: number;
4
+ variant?: "short" | "narrow";
5
+ }, {}, "">;
6
+ export default FormatBytes;
@@ -6,7 +6,7 @@
6
6
 
7
7
  type Props = {
8
8
  size: HeadingSize;
9
- color?: Color;
9
+ color?: Color | 'muted';
10
10
  class?: string;
11
11
 
12
12
  children: Snippet;
@@ -27,6 +27,7 @@
27
27
  base: 'font-bold leading-none tracking-tight',
28
28
  variants: {
29
29
  color: {
30
+ muted: 'text-gray-600 dark:text-gray-400',
30
31
  primary: 'text-primary',
31
32
  secondary: 'text-dark',
32
33
  success: 'text-success',
@@ -2,7 +2,7 @@ import type { Color, HeadingSize } from '../../types.js';
2
2
  import type { Snippet } from 'svelte';
3
3
  declare const Heading: import("svelte").Component<{
4
4
  size: HeadingSize;
5
- color?: Color;
5
+ color?: Color | "muted";
6
6
  class?: string;
7
7
  children: Snippet;
8
8
  }, {}, "">;
@@ -0,0 +1,54 @@
1
+ <script lang="ts">
2
+ import type { Color, Size } from '../../types.js';
3
+ import { cleanClass } from '../../utils.js';
4
+ import { tv } from 'tailwind-variants';
5
+
6
+ interface Props {
7
+ size?: Size;
8
+ color?: Color;
9
+ class?: string;
10
+ }
11
+
12
+ let { size = 'medium', color = 'primary', class: className }: Props = $props();
13
+
14
+ const styles = tv({
15
+ base: 'animate-spin fill-primary text-gray-400 dark:text-gray-600',
16
+ variants: {
17
+ size: {
18
+ tiny: 'h-3',
19
+ small: 'h-4',
20
+ medium: 'h-5',
21
+ large: 'h-6',
22
+ giant: 'h-12',
23
+ },
24
+ color: {
25
+ primary: 'fill-primary',
26
+ secondary: 'fill-dark',
27
+ success: 'fill-success',
28
+ danger: 'fill-danger',
29
+ warning: 'fill-warning',
30
+ info: 'fill-info',
31
+ },
32
+ },
33
+ });
34
+ </script>
35
+
36
+ <div>
37
+ <svg
38
+ role="status"
39
+ class={cleanClass(styles({ color, size }), className)}
40
+ viewBox="0 0 100 101"
41
+ fill="none"
42
+ xmlns="http://www.w3.org/2000/svg"
43
+ data-testid="loading-spinner"
44
+ >
45
+ <path
46
+ d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
47
+ fill="currentColor"
48
+ />
49
+ <path
50
+ d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
51
+ fill="currentFill"
52
+ />
53
+ </svg>
54
+ </div>
@@ -0,0 +1,7 @@
1
+ import type { Color, Size } from '../../types.js';
2
+ declare const LoadingSpinner: import("svelte").Component<{
3
+ size?: Size;
4
+ color?: Color;
5
+ class?: string;
6
+ }, {}, "">;
7
+ export default LoadingSpinner;
@@ -1,30 +1,30 @@
1
1
  <script lang="ts">
2
- import icon from '../../assets/immich-logo.svg';
3
2
  import inlineDark from '../../assets/immich-logo-inline-dark.svg';
4
3
  import inlineLight from '../../assets/immich-logo-inline-light.svg';
5
4
  import stackedDark from '../../assets/immich-logo-stacked-dark.svg';
6
5
  import stackedLight from '../../assets/immich-logo-stacked-light.svg';
7
- import type { Size } from '../../types.js';
6
+ import icon from '../../assets/immich-logo.svg';
7
+ import { theme } from '../../services/theme.svelte.js';
8
+ import { Theme, type Size } from '../../types.js';
8
9
  import { cleanClass } from '../../utils.js';
9
10
  import { tv } from 'tailwind-variants';
10
11
 
11
12
  type Props = {
12
13
  size?: Size | 'landing';
13
- theme?: 'dark' | 'light';
14
14
  variant?: 'stacked' | 'inline' | 'logo' | 'icon';
15
15
  class?: string;
16
16
  };
17
17
 
18
- const { theme = 'light', variant = 'logo', size = 'medium', class: className }: Props = $props();
18
+ const { variant = 'logo', size = 'medium', class: className }: Props = $props();
19
19
 
20
- const getUrl = ({ theme, variant }: Required<Pick<Props, 'theme' | 'variant'>>): string => {
20
+ const getUrl = (variant: Props['variant']): string => {
21
21
  switch (variant) {
22
22
  case 'stacked': {
23
- return theme === 'light' ? stackedLight : stackedDark;
23
+ return theme.value === Theme.Light ? stackedLight : stackedDark;
24
24
  }
25
25
 
26
26
  case 'inline': {
27
- return theme === 'light' ? inlineLight : inlineDark;
27
+ return theme.value === Theme.Light ? inlineLight : inlineDark;
28
28
  }
29
29
 
30
30
  default: {
@@ -53,7 +53,7 @@
53
53
  },
54
54
  });
55
55
 
56
- const src = $derived(getUrl({ theme, variant }));
56
+ const src = $derived(getUrl(variant));
57
57
  </script>
58
58
 
59
59
  <img {src} class={cleanClass(styles({ size, variant }), className)} alt="Immich logo" />
@@ -1,7 +1,6 @@
1
- import type { Size } from '../../types.js';
1
+ import { type Size } from '../../types.js';
2
2
  declare const Logo: import("svelte").Component<{
3
3
  size?: Size | "landing";
4
- theme?: "dark" | "light";
5
4
  variant?: "stacked" | "inline" | "logo" | "icon";
6
5
  class?: string;
7
6
  }, {}, "">;
@@ -0,0 +1,15 @@
1
+ <script lang="ts">
2
+ import InternalSelect from '../../internal/Select.svelte';
3
+ import type { MultiSelectProps, SelectItem } from '../../types.js';
4
+
5
+ type T = SelectItem;
6
+
7
+ let { value = $bindable(), onChange, ...restProps }: MultiSelectProps<T> = $props();
8
+
9
+ const handleChange = (items: T[]) => {
10
+ value = items;
11
+ onChange?.(value);
12
+ };
13
+ </script>
14
+
15
+ <InternalSelect multiple onChange={handleChange} {...restProps} />
@@ -0,0 +1,3 @@
1
+ import type { MultiSelectProps, SelectItem } from '../../types.js';
2
+ declare const MultiSelect: import("svelte").Component<MultiSelectProps<SelectItem>, {}, "value">;
3
+ export default MultiSelect;
@@ -0,0 +1,12 @@
1
+ <script lang="ts">
2
+ type Props = {
3
+ title: string;
4
+ };
5
+
6
+ let { title }: Props = $props();
7
+ </script>
8
+
9
+ <div class="text-xs transition-all duration-200">
10
+ <p class="hidden px-6 py-4 uppercase group-hover:sm:block md:block">{title}</p>
11
+ <hr class="mx-4 mb-[31px] mt-8 block group-hover:sm:hidden md:hidden" />
12
+ </div>
@@ -0,0 +1,4 @@
1
+ declare const NavbarGroup: import("svelte").Component<{
2
+ title: string;
3
+ }, {}, "">;
4
+ export default NavbarGroup;
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ import Icon from '../Icon/Icon.svelte';
3
+ import type { IconProps } from '../../types.js';
4
+ import { tv } from 'tailwind-variants';
5
+
6
+ type Props = {
7
+ title: string;
8
+ active?: boolean;
9
+ href: string;
10
+ } & IconProps;
11
+
12
+ let { href, title, active = false, ...iconProps }: Props = $props();
13
+
14
+ const styles = tv({
15
+ base: 'hover:bg-subtle hover:text-primary flex w-full place-items-center gap-4 rounded-r-full py-3 transition-[padding] delay-100 duration-100 pl-5 group-hover:sm:px-5 md:px-5',
16
+ variants: {
17
+ active: {
18
+ true: 'bg-primary/10 text-primary',
19
+ false: '',
20
+ },
21
+ },
22
+ });
23
+ </script>
24
+
25
+ <a {href} draggable="false" aria-current={active ? 'page' : undefined} class={styles({ active })}>
26
+ <div class="flex w-full place-items-center gap-4 overflow-hidden truncate">
27
+ <Icon size="1.5em" class="shrink-0" aria-hidden={true} {...iconProps} />
28
+ <span class="text-sm font-medium">{title}</span>
29
+ </div>
30
+ </a>
@@ -0,0 +1,7 @@
1
+ import type { IconProps } from '../../types.js';
2
+ declare const NavbarItem: import("svelte").Component<{
3
+ title: string;
4
+ active?: boolean;
5
+ href: string;
6
+ } & IconProps, {}, "">;
7
+ export default NavbarItem;