@immich/ui 0.24.6 → 0.25.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.
@@ -2,7 +2,7 @@
2
2
  import Button from '../../internal/Button.svelte';
3
3
  import type { ButtonProps } from '../../types.js';
4
4
 
5
- const props: ButtonProps = $props();
5
+ let { ref = $bindable(null), ...props }: ButtonProps = $props();
6
6
  </script>
7
7
 
8
- <Button {...props} />
8
+ <Button bind:ref {...props} />
@@ -1,5 +1,5 @@
1
1
  import Button from '../../internal/Button.svelte';
2
2
  import type { ButtonProps } from '../../types.js';
3
- declare const Button: import("svelte").Component<ButtonProps, {}, "">;
3
+ declare const Button: import("svelte").Component<ButtonProps, {}, "ref">;
4
4
  type Button = ReturnType<typeof Button>;
5
5
  export default Button;
@@ -12,6 +12,7 @@
12
12
  import { tv } from 'tailwind-variants';
13
13
 
14
14
  type Props = HTMLAttributes<HTMLDivElement> & {
15
+ ref?: HTMLElement | null;
15
16
  color?: Color;
16
17
  shape?: 'round' | 'rectangle';
17
18
  expanded?: boolean;
@@ -20,6 +21,7 @@
20
21
  };
21
22
 
22
23
  let {
24
+ ref = $bindable(null),
23
25
  color,
24
26
  class: className,
25
27
  shape = 'round',
@@ -125,7 +127,11 @@
125
127
  {/if}
126
128
  {/snippet}
127
129
 
128
- <div class={cleanClass(containerStyles({ shape, border: !color }), className)} {...restProps}>
130
+ <div
131
+ bind:this={ref}
132
+ class={cleanClass(containerStyles({ shape, border: !color }), className)}
133
+ {...restProps}
134
+ >
129
135
  <div class={cleanClass(cardStyles({ color }))}>
130
136
  {#if headerChild}
131
137
  {@render header()}
@@ -2,12 +2,13 @@ import type { Color } from '../../types.js';
2
2
  import { type Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
4
  type Props = HTMLAttributes<HTMLDivElement> & {
5
+ ref?: HTMLElement | null;
5
6
  color?: Color;
6
7
  shape?: 'round' | 'rectangle';
7
8
  expanded?: boolean;
8
9
  expandable?: boolean;
9
10
  children: Snippet;
10
11
  };
11
- declare const Card: import("svelte").Component<Props, {}, "expanded">;
12
+ declare const Card: import("svelte").Component<Props, {}, "ref" | "expanded">;
12
13
  type Card = ReturnType<typeof Card>;
13
14
  export default Card;
@@ -16,21 +16,25 @@
16
16
  import { tv } from 'tailwind-variants';
17
17
 
18
18
  type Props = {
19
- title: string;
19
+ title?: string;
20
+ icon?: string | boolean;
20
21
  size?: ModalSize;
21
22
  class?: string;
22
- icon?: string | boolean;
23
23
  expandable?: boolean;
24
+ closeOnEsc?: boolean;
25
+ closeOnBackdropClick?: boolean;
24
26
  children: Snippet;
25
27
  onClose?: () => void;
26
28
  };
27
29
 
28
30
  let {
29
- title,
30
31
  size = 'medium',
31
- icon = true,
32
32
  onClose,
33
+ icon = true,
34
+ title,
33
35
  class: className,
36
+ closeOnEsc = true,
37
+ closeOnBackdropClick = false,
34
38
  children,
35
39
  }: Props = $props();
36
40
 
@@ -49,6 +53,7 @@
49
53
  });
50
54
 
51
55
  const { getChildren: getChildSnippet } = withChildrenSnippets(ChildKey.Modal);
56
+ const headerChildren = $derived(getChildSnippet(ChildKey.ModalHeader));
52
57
  const bodyChildren = $derived(getChildSnippet(ChildKey.ModalBody));
53
58
  const footerChildren = $derived(getChildSnippet(ChildKey.ModalFooter));
54
59
 
@@ -59,6 +64,16 @@
59
64
 
60
65
  onClose?.();
61
66
  };
67
+
68
+ let cardRef = $state<HTMLElement | null>(null);
69
+
70
+ const handleCloseOnClick = (event: Event) => {
71
+ if (!closeOnBackdropClick || cardRef?.contains(event.target as Node)) {
72
+ return;
73
+ }
74
+
75
+ onClose?.();
76
+ };
62
77
  </script>
63
78
 
64
79
  <Dialog.Root open={true}>
@@ -69,25 +84,34 @@
69
84
  if (e.key === 'Escape') {
70
85
  e.stopPropagation();
71
86
  e.preventDefault();
72
- handleClose();
87
+ if (closeOnEsc) {
88
+ handleClose();
89
+ }
73
90
  }
74
91
  }}
92
+ onclick={handleCloseOnClick}
75
93
  class={cleanClass(
76
94
  'fixed start-0 top-0 flex h-dvh w-screen items-center justify-center overflow-hidden sm:p-4',
77
95
  )}
78
96
  >
79
97
  <div class={cleanClass('flex h-full w-full flex-col items-center justify-center')}>
80
- <Card class={cleanClass(modalStyles({ size }), className)}>
98
+ <Card bind:ref={cardRef} class={cleanClass(modalStyles({ size }), className)}>
81
99
  <CardHeader class="border-b border-gray-200 px-5 py-3 dark:border-white/10">
82
- <div class="flex items-center justify-between gap-2">
83
- {#if typeof icon === 'string'}
84
- <Icon {icon} size="1.5rem" aria-hidden />
85
- {:else if icon}
86
- <Logo variant="icon" size="tiny" />
87
- {/if}
88
- <CardTitle tag="p" class="text-dark/90 grow text-lg font-semibold">{title}</CardTitle>
89
- <CloseButton class="-me-2" onclick={() => handleClose()} />
90
- </div>
100
+ {#if headerChildren}
101
+ {@render headerChildren.snippet()}
102
+ {:else if title}
103
+ <div class="flex items-center justify-between gap-2">
104
+ {#if typeof icon === 'string'}
105
+ <Icon {icon} size="1.5rem" aria-hidden />
106
+ {:else if icon}
107
+ <Logo variant="icon" size="tiny" />
108
+ {/if}
109
+ <CardTitle tag="p" class="text-dark/90 grow text-lg font-semibold"
110
+ >{title}</CardTitle
111
+ >
112
+ <CloseButton class="-me-2" onclick={() => handleClose()} />
113
+ </div>
114
+ {/if}
91
115
  </CardHeader>
92
116
 
93
117
  <CardBody class="grow px-5">
@@ -1,11 +1,13 @@
1
1
  import type { ModalSize } from '../../types.js';
2
2
  import { type Snippet } from 'svelte';
3
3
  type Props = {
4
- title: string;
4
+ title?: string;
5
+ icon?: string | boolean;
5
6
  size?: ModalSize;
6
7
  class?: string;
7
- icon?: string | boolean;
8
8
  expandable?: boolean;
9
+ closeOnEsc?: boolean;
10
+ closeOnBackdropClick?: boolean;
9
11
  children: Snippet;
10
12
  onClose?: () => void;
11
13
  };
package/dist/index.d.ts CHANGED
@@ -39,6 +39,7 @@ export { default as Link } from './components/Link/Link.svelte';
39
39
  export { default as LoadingSpinner } from './components/LoadingSpinner/LoadingSpinner.svelte';
40
40
  export { default as Logo } from './components/Logo/Logo.svelte';
41
41
  export { default as Modal } from './components/Modal/Modal.svelte';
42
+ export { default as ModalHeader } from './components/Modal/ModalHeader.svelte';
42
43
  export { default as ModalBody } from './components/Modal/ModalBody.svelte';
43
44
  export { default as ModalFooter } from './components/Modal/ModalFooter.svelte';
44
45
  export { default as MultiSelect } from './components/MultiSelect/MultiSelect.svelte';
package/dist/index.js CHANGED
@@ -41,6 +41,7 @@ export { default as Link } from './components/Link/Link.svelte';
41
41
  export { default as LoadingSpinner } from './components/LoadingSpinner/LoadingSpinner.svelte';
42
42
  export { default as Logo } from './components/Logo/Logo.svelte';
43
43
  export { default as Modal } from './components/Modal/Modal.svelte';
44
+ export { default as ModalHeader } from './components/Modal/ModalHeader.svelte';
44
45
  export { default as ModalBody } from './components/Modal/ModalBody.svelte';
45
46
  export { default as ModalFooter } from './components/Modal/ModalFooter.svelte';
46
47
  export { default as MultiSelect } from './components/MultiSelect/MultiSelect.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immich/ui",
3
- "version": "0.24.6",
3
+ "version": "0.25.0",
4
4
  "license": "GNU Affero General Public License version 3",
5
5
  "scripts": {
6
6
  "create": "node scripts/create.js",