@immich/ui 0.50.1 → 0.51.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.
@@ -83,7 +83,7 @@
83
83
  {#if open}
84
84
  <Card {color} class={cleanClass(className)}>
85
85
  <CardBody>
86
- <div class="flex items-center justify-between">
86
+ <div class={cleanClass((closable || onClose) && 'flex items-center justify-between')}>
87
87
  <div class={cleanClass('flex gap-2')}>
88
88
  {#if icon}
89
89
  <div>
@@ -24,7 +24,7 @@
24
24
  }: Props = $props();
25
25
 
26
26
  const styles = tv({
27
- base: 'text-light inline-flex items-center border font-medium antialiased',
27
+ base: 'text-light inline-flex items-center font-medium antialiased',
28
28
  variants: {
29
29
  shape: styleVariants.shape,
30
30
  color: {
@@ -37,11 +37,11 @@
37
37
  },
38
38
  textSize: styleVariants.textSize,
39
39
  paddingSize: {
40
- tiny: 'px-1.5 py-1',
41
- small: 'px-1.75 py-0.75',
42
- medium: 'px-2.5 py-1',
43
- large: 'px-2.75 py-1',
44
- giant: 'px-3 py-1.25',
40
+ tiny: 'px-3 py-1',
41
+ small: 'px-3.25 py-1.25',
42
+ medium: 'px-3.75 py-1.5',
43
+ large: 'px-4 py-1.75',
44
+ giant: 'px-4.25 py-2',
45
45
  },
46
46
  roundedSize: {
47
47
  tiny: 'rounded-lg',
@@ -52,8 +52,8 @@
52
52
  primary: 'bg-primary-50 dark:bg-primary-100',
53
53
  secondary: 'text-dark bg-light-50 dark:bg-light-100 dark:text-white',
54
54
  success: 'bg-success-50 dark:bg-success-100',
55
- danger: 'bg-danger-50 dark:bg-danger-100',
56
- warning: 'bg-warning-50 dark:bg-warning-100',
55
+ danger: 'bg-danger-100',
56
+ warning: 'bg-warning-100',
57
57
  info: 'bg-info-50 dark:bg-info-100',
58
58
  },
59
59
  },
@@ -60,9 +60,9 @@
60
60
  >
61
61
  {/if}
62
62
  <div class="mt-2">
63
- <Badge color="primary" size="small">{item.type}</Badge>
63
+ <Badge color="primary" size="small" shape="round">{item.type}</Badge>
64
64
  {#if item.isGlobal}
65
- <Badge color="warning" size="small">Global</Badge>
65
+ <Badge color="warning" size="small" shape="round">Global</Badge>
66
66
  {/if}
67
67
  </div>
68
68
  </div>
@@ -1,11 +1,11 @@
1
1
  <script lang="ts">
2
- import Button from '../../internal/Button.svelte';
3
2
  import Icon from '../Icon/Icon.svelte';
3
+ import Button from '../../internal/Button.svelte';
4
4
  import type { IconButtonProps } from '../../types.js';
5
5
 
6
6
  const { icon, flipped, flopped, title, 'aria-label': ariaLabel, ...buttonProps }: IconButtonProps = $props();
7
7
 
8
- const buttonTitle = $derived(title || ariaLabel);
8
+ const buttonTitle = $derived(title ?? ariaLabel);
9
9
  </script>
10
10
 
11
11
  <Button icon {...buttonProps} title={buttonTitle} aria-label={ariaLabel}>
@@ -8,6 +8,7 @@
8
8
  import CloseButton from '../CloseButton/CloseButton.svelte';
9
9
  import Icon from '../Icon/Icon.svelte';
10
10
  import Logo from '../Logo/Logo.svelte';
11
+ import TooltipProvider from '../Tooltip/TooltipProvider.svelte';
11
12
  import { ChildKey, zIndex } from '../../constants.js';
12
13
  import { commandPaletteManager } from '../../services/command-palette-manager.svelte.js';
13
14
  import type { ModalSize } from '../../types.js';
@@ -99,35 +100,37 @@
99
100
  {interactOutsideBehavior}
100
101
  class={cleanClass(modalContentStyles({ size }))}
101
102
  >
102
- <div class={cleanClass('flex w-full grow flex-col justify-center')}>
103
- <Card bind:ref={cardRef} class={cleanClass(modalStyles({ size }), className)}>
104
- <CardHeader class="border-b border-gray-200 px-5 py-3 dark:border-white/10">
105
- {#if headerChildren}
106
- {@render headerChildren.snippet()}
107
- {:else if title}
108
- <div class="flex items-center justify-between gap-2">
109
- {#if typeof icon === 'string'}
110
- <Icon {icon} size="1.5rem" aria-hidden />
111
- {:else if icon}
112
- <Logo variant="icon" size="tiny" />
113
- {/if}
114
- <CardTitle tag="p" class="text-dark/90 grow text-lg font-semibold">{title}</CardTitle>
115
- <CloseButton class="-me-2" onclick={() => handleClose()} />
116
- </div>
117
- {/if}
118
- </CardHeader>
103
+ <TooltipProvider>
104
+ <div class={cleanClass('flex w-full grow flex-col justify-center')}>
105
+ <Card bind:ref={cardRef} class={cleanClass(modalStyles({ size }), className)}>
106
+ <CardHeader class="border-b border-gray-200 px-5 py-3 dark:border-white/10">
107
+ {#if headerChildren}
108
+ {@render headerChildren.snippet()}
109
+ {:else if title}
110
+ <div class="flex items-center justify-between gap-2">
111
+ {#if typeof icon === 'string'}
112
+ <Icon {icon} size="1.5rem" aria-hidden />
113
+ {:else if icon}
114
+ <Logo variant="icon" size="tiny" />
115
+ {/if}
116
+ <CardTitle tag="p" class="text-dark/90 grow text-lg font-semibold">{title}</CardTitle>
117
+ <CloseButton class="-me-2" onclick={() => handleClose()} />
118
+ </div>
119
+ {/if}
120
+ </CardHeader>
119
121
 
120
- <CardBody class="grow px-5">
121
- {@render bodyChildren?.snippet()}
122
- </CardBody>
122
+ <CardBody class="grow px-5">
123
+ {@render bodyChildren?.snippet()}
124
+ </CardBody>
123
125
 
124
- {#if footerChildren}
125
- <CardFooter class="border-t border-gray-200 dark:border-white/10">
126
- {@render footerChildren.snippet()}
127
- </CardFooter>
128
- {/if}
129
- </Card>
130
- </div>
126
+ {#if footerChildren}
127
+ <CardFooter class="border-t border-gray-200 dark:border-white/10">
128
+ {@render footerChildren.snippet()}
129
+ </CardFooter>
130
+ {/if}
131
+ </Card>
132
+ </div>
133
+ </TooltipProvider>
131
134
  </Dialog.Content>
132
135
  </Dialog.Portal>
133
136
  </Dialog.Root>
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ import { zIndex } from '../../constants.js';
3
+ import { Tooltip } from 'bits-ui';
4
+ import type { Snippet } from 'svelte';
5
+ import { fly } from 'svelte/transition';
6
+
7
+ type Props = Tooltip.RootProps & {
8
+ text?: string | null;
9
+ child: Snippet<[{ props: Record<string, unknown> }]>;
10
+ };
11
+
12
+ let { open = $bindable(false), child, text, ...restProps }: Props = $props();
13
+ </script>
14
+
15
+ {#if text}
16
+ <Tooltip.Root bind:open {...restProps}>
17
+ <Tooltip.Trigger {child} />
18
+ <Tooltip.Portal>
19
+ <Tooltip.Content
20
+ sideOffset={8}
21
+ forceMount
22
+ class="bg-light-800 text-light {zIndex.Tooltip} rounded-lg px-3.5 py-2 text-xs shadow-lg"
23
+ >
24
+ {#snippet child({ wrapperProps, props, open })}
25
+ {#if open}
26
+ <div {...wrapperProps}>
27
+ <div {...props} transition:fly={{ y: 4, duration: 150 }}>
28
+ {text}
29
+ </div>
30
+ </div>
31
+ {/if}
32
+ {/snippet}
33
+ </Tooltip.Content>
34
+ </Tooltip.Portal>
35
+ </Tooltip.Root>
36
+ {:else}
37
+ {@render child({ props: {} })}
38
+ {/if}
@@ -0,0 +1,11 @@
1
+ import { Tooltip } from 'bits-ui';
2
+ import type { Snippet } from 'svelte';
3
+ type Props = Tooltip.RootProps & {
4
+ text?: string | null;
5
+ child: Snippet<[{
6
+ props: Record<string, unknown>;
7
+ }]>;
8
+ };
9
+ declare const Tooltip: import("svelte").Component<Props, {}, "open">;
10
+ type Tooltip = ReturnType<typeof Tooltip>;
11
+ export default Tooltip;
@@ -0,0 +1,15 @@
1
+ <script lang="ts">
2
+ import { Tooltip } from 'bits-ui';
3
+ import type { Snippet } from 'svelte';
4
+
5
+ type Props = {
6
+ delayDuration?: number;
7
+ children?: Snippet;
8
+ };
9
+
10
+ const { children, delayDuration }: Props = $props();
11
+ </script>
12
+
13
+ <Tooltip.Provider disableCloseOnTriggerClick ignoreNonKeyboardFocus {delayDuration}>
14
+ {@render children?.()}
15
+ </Tooltip.Provider>
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Props = {
3
+ delayDuration?: number;
4
+ children?: Snippet;
5
+ };
6
+ declare const TooltipProvider: import("svelte").Component<Props, {}, "">;
7
+ type TooltipProvider = ReturnType<typeof TooltipProvider>;
8
+ export default TooltipProvider;
@@ -20,4 +20,5 @@ export declare const zIndex: {
20
20
  SelectDropdown: string;
21
21
  ToastPanel: string;
22
22
  ContextMenu: string;
23
+ Tooltip: string;
23
24
  };
package/dist/constants.js CHANGED
@@ -21,4 +21,5 @@ export const zIndex = {
21
21
  SelectDropdown: 'z-55',
22
22
  ToastPanel: 'z-60',
23
23
  ContextMenu: 'z-70!',
24
+ Tooltip: 'z-90',
24
25
  };
package/dist/index.d.ts CHANGED
@@ -73,6 +73,8 @@ export { default as Toast } from './components/Toast/Toast.svelte';
73
73
  export { default as ToastContainer } from './components/Toast/ToastContainer.svelte';
74
74
  export { default as ToastContent } from './components/Toast/ToastContent.svelte';
75
75
  export { default as ToastPanel } from './components/Toast/ToastPanel.svelte';
76
+ export { default as Tooltip } from './components/Tooltip/Tooltip.svelte';
77
+ export { default as TooltipProvider } from './components/Tooltip/TooltipProvider.svelte';
76
78
  export * from './services/command-palette-manager.svelte.js';
77
79
  export * from './services/menu-manager.svelte.js';
78
80
  export * from './services/modal-manager.svelte.js';
package/dist/index.js CHANGED
@@ -75,6 +75,8 @@ export { default as Toast } from './components/Toast/Toast.svelte';
75
75
  export { default as ToastContainer } from './components/Toast/ToastContainer.svelte';
76
76
  export { default as ToastContent } from './components/Toast/ToastContent.svelte';
77
77
  export { default as ToastPanel } from './components/Toast/ToastPanel.svelte';
78
+ export { default as Tooltip } from './components/Tooltip/Tooltip.svelte';
79
+ export { default as TooltipProvider } from './components/Tooltip/TooltipProvider.svelte';
78
80
  // helpers
79
81
  export * from './services/command-palette-manager.svelte.js';
80
82
  export * from './services/menu-manager.svelte.js';
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import Icon from '../components/Icon/Icon.svelte';
3
3
  import LoadingSpinner from '../components/LoadingSpinner/LoadingSpinner.svelte';
4
+ import Tooltip from '../components/Tooltip/Tooltip.svelte';
4
5
  import { styleVariants } from '../styles.js';
5
6
  import type { ButtonProps, Size } from '../types.js';
6
7
  import { isExternalLink, resolveUrl } from '../utilities/common.js';
@@ -27,6 +28,7 @@
27
28
  leadingIcon,
28
29
  trailingIcon,
29
30
  icon = false,
31
+ title,
30
32
  class: className = '',
31
33
  children,
32
34
  ...restProps
@@ -149,29 +151,35 @@
149
151
  {/if}
150
152
  {/snippet}
151
153
 
152
- {#if href}
153
- {@const resolved = resolveUrl(href)}
154
- {@const external = isExternalLink(resolved)}
155
- <a
156
- bind:this={ref}
157
- href={resolved}
158
- class={classList}
159
- aria-disabled={disabled}
160
- target={external ? '_blank' : undefined}
161
- rel={external ? 'noopener noreferrer' : undefined}
162
- {...restProps as HTMLAnchorAttributes}
163
- >
164
- {@render wrapper()}
165
- </a>
166
- {:else}
167
- <ButtonPrimitive.Root
168
- bind:ref
169
- class={classList}
170
- type={type as HTMLButtonAttributes['type']}
171
- {...restProps as HTMLButtonAttributes}
172
- {disabled}
173
- aria-disabled={disabled}
174
- >
175
- {@render wrapper()}
176
- </ButtonPrimitive.Root>
177
- {/if}
154
+ <Tooltip text={title}>
155
+ {#snippet child({ props })}
156
+ {#if href}
157
+ {@const resolved = resolveUrl(href)}
158
+ {@const external = isExternalLink(resolved)}
159
+ <a
160
+ bind:this={ref}
161
+ {...props}
162
+ href={resolved}
163
+ class={classList}
164
+ aria-disabled={disabled}
165
+ target={external ? '_blank' : undefined}
166
+ rel={external ? 'noopener noreferrer' : undefined}
167
+ {...restProps as HTMLAnchorAttributes}
168
+ >
169
+ {@render wrapper()}
170
+ </a>
171
+ {:else}
172
+ <ButtonPrimitive.Root
173
+ bind:ref
174
+ {...props}
175
+ class={classList}
176
+ type={type as HTMLButtonAttributes['type']}
177
+ {...restProps as HTMLButtonAttributes}
178
+ {disabled}
179
+ aria-disabled={disabled}
180
+ >
181
+ {@render wrapper()}
182
+ </ButtonPrimitive.Root>
183
+ {/if}
184
+ {/snippet}
185
+ </Tooltip>
@@ -136,7 +136,7 @@
136
136
  >
137
137
  {#snippet children({ selected })}
138
138
  <div
139
- class="flex cursor-pointer items-center justify-center gap-2 text-sm font-medium whitespace-nowrap transition-colors"
139
+ class="flex items-center justify-center gap-2 text-sm font-medium whitespace-nowrap transition-colors"
140
140
  >
141
141
  <span>{label}</span>
142
142
  </div>
@@ -24,7 +24,14 @@ export const resolveUrl = (url, currentHostname) => {
24
24
  }
25
25
  };
26
26
  export const isExternalLink = (href) => {
27
- return !(href.startsWith('/') || href.startsWith('#'));
27
+ try {
28
+ const current = new URL(globalThis.location.href);
29
+ const target = new URL(href, current);
30
+ return target.origin !== current.origin;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
28
35
  };
29
36
  export const isMenuItemType = (item) => {
30
37
  return item === MenuItemType.Divider;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immich/ui",
3
- "version": "0.50.1",
3
+ "version": "0.51.0",
4
4
  "license": "GNU Affero General Public License version 3",
5
5
  "repository": {
6
6
  "type": "git",
@@ -51,7 +51,7 @@
51
51
  "@mdi/js": "^7.4.47",
52
52
  "bits-ui": "^2.9.8",
53
53
  "luxon": "^3.7.2",
54
- "simple-icons": "^15.14.0",
54
+ "simple-icons": "^16.0.0",
55
55
  "svelte-highlight": "^7.8.4",
56
56
  "tailwind-merge": "^3.0.0",
57
57
  "tailwind-variants": "^3.0.0",