@immich/ui 0.49.3 → 0.50.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.
@@ -18,7 +18,7 @@
18
18
  <Scrollable
19
19
  class={cleanClass(
20
20
  'bg-light text-dark absolute shrink-0 border-e shadow-lg transition-all duration-200 md:relative',
21
- open ? `${zIndex.AppShellSidebar} w-[min(100vw,16rem)]` : 'w-[0px] border-e-0',
21
+ open ? `${zIndex.AppShellSidebar} w-[min(100vw,16rem)]` : 'w-0 border-e-0',
22
22
  className,
23
23
  )}
24
24
  >
@@ -11,7 +11,7 @@
11
11
  const { color = 'primary', size = 'medium', name }: Props = $props();
12
12
 
13
13
  const styles = tv({
14
- base: 'flex h-full w-full items-center justify-center font-medium select-none',
14
+ base: 'flex h-full w-full items-center justify-center font-medium text-white select-none',
15
15
  variants: {
16
16
  size: {
17
17
  tiny: 'h-5 w-5 text-xs',
@@ -22,15 +22,15 @@
22
22
  },
23
23
  color: {
24
24
  primary: 'bg-primary text-light',
25
- pink: 'text-light bg-pink-400',
26
- red: 'text-light bg-red-500',
27
- yellow: 'text-light bg-yellow-500',
28
- purple: 'text-dark bg-purple-600',
29
- orange: 'text-light bg-orange-600',
30
- gray: 'text-dark bg-gray-600',
31
- amber: 'text-light bg-amber-600',
32
- blue: 'text-dark bg-blue-500',
33
- green: 'text-dark bg-green-600',
25
+ pink: 'bg-pink-400',
26
+ red: 'bg-red-500',
27
+ yellow: 'bg-yellow-500',
28
+ purple: 'bg-purple-600',
29
+ orange: 'bg-orange-600',
30
+ gray: 'bg-gray-600',
31
+ amber: 'bg-amber-600',
32
+ blue: 'bg-blue-500',
33
+ green: 'bg-green-600',
34
34
  },
35
35
  },
36
36
  });
@@ -24,31 +24,31 @@
24
24
  }: Props = $props();
25
25
 
26
26
  const styles = tv({
27
- base: 'border px-2 py-0.5',
27
+ base: 'text-light inline-flex items-center border font-medium antialiased',
28
28
  variants: {
29
29
  shape: styleVariants.shape,
30
30
  color: {
31
- primary: 'border-primary-100 bg-primary-100 text-primary-950 dark:bg-primary-200 dark:border-primary-200',
32
- secondary: 'border-light-300 bg-light-100 text-light-950 dark:bg-light-200',
33
- success: 'border-success-100 bg-success-100 text-success-950 dark:bg-success-200 dark:border-success-200',
34
- info: 'border-info-200 bg-info-200 text-info-950',
35
- warning: 'border-warning-200 bg-warning-200 text-warning-950',
36
- danger: 'border-danger-200 bg-danger-200 text-danger-950 dark:bg-danger-200',
31
+ primary: 'bg-primary',
32
+ secondary: 'bg-dark',
33
+ success: 'bg-success',
34
+ info: 'bg-info',
35
+ warning: 'bg-warning',
36
+ danger: 'bg-danger text-danger-50',
37
37
  },
38
38
  textSize: styleVariants.textSize,
39
39
  paddingSize: {
40
- tiny: 'px-1.5 py-px',
41
- small: 'px-1.75 py-0.5',
42
- medium: 'px-2.5 py-0.75',
40
+ tiny: 'px-1.5 py-1',
41
+ small: 'px-1.75 py-0.75',
42
+ medium: 'px-2.5 py-1',
43
43
  large: 'px-2.75 py-1',
44
44
  giant: 'px-3 py-1.25',
45
45
  },
46
46
  roundedSize: {
47
- tiny: 'rounded-md',
48
- small: 'rounded-md',
49
- medium: 'rounded-md',
50
- large: 'rounded-lg',
51
- giant: 'rounded-lg',
47
+ tiny: 'rounded-lg',
48
+ small: 'rounded-lg',
49
+ medium: 'rounded-lg',
50
+ large: 'rounded-xl',
51
+ giant: 'rounded-xl',
52
52
  },
53
53
  },
54
54
  });
@@ -19,7 +19,7 @@
19
19
  </span>
20
20
  {/snippet}
21
21
 
22
- <nav class={cleanClass('flex items-center gap-1', className)} {...props}>
22
+ <nav class={cleanClass('flex flex-wrap items-center gap-1', className)} {...props}>
23
23
  {#each items as item, index (index)}
24
24
  {#if index > 0}
25
25
  {#if typeof separator === 'object' && 'text' in separator}
@@ -9,7 +9,6 @@
9
9
  import { cubicOut } from 'svelte/easing';
10
10
  import type { HTMLAttributes } from 'svelte/elements';
11
11
  import { slide } from 'svelte/transition';
12
- import { twMerge } from 'tailwind-merge';
13
12
  import { tv } from 'tailwind-variants';
14
13
 
15
14
  type Props = HTMLAttributes<HTMLDivElement> & {
@@ -87,14 +86,12 @@
87
86
  const headerPadding = $derived(headerBorder || !expanded);
88
87
 
89
88
  const headerContainerClasses = $derived(
90
- twMerge(
91
- cleanClass(
92
- headerContainerStyles({
93
- padding: headerPadding,
94
- border: headerBorder,
95
- }),
96
- headerChild?.class,
97
- ),
89
+ cleanClass(
90
+ headerContainerStyles({
91
+ padding: headerPadding,
92
+ border: headerBorder,
93
+ }),
94
+ headerChild?.class,
98
95
  ),
99
96
  );
100
97
  </script>
@@ -137,14 +134,14 @@
137
134
  {#if bodyChild && expanded}
138
135
  <div
139
136
  transition:slide={{ duration: expandable ? 200 : 0, easing: cubicOut }}
140
- class={twMerge(cleanClass('immich-scrollbar h-full w-full overflow-auto p-4', bodyChild?.class))}
137
+ class={cleanClass('immich-scrollbar h-full w-full overflow-auto p-4', bodyChild?.class)}
141
138
  >
142
139
  {@render bodyChild?.snippet()}
143
140
  </div>
144
141
  {/if}
145
142
 
146
143
  {#if footerChild}
147
- <div class={twMerge(cleanClass('flex items-center border-t p-4', footerChild.class))}>
144
+ <div class={cleanClass('flex items-center border-t p-4', footerChild.class)}>
148
145
  {@render footerChild.snippet()}
149
146
  </div>
150
147
  {/if}
@@ -43,11 +43,13 @@
43
43
  fullWidth
44
44
  variant={selected ? 'outline' : 'ghost'}
45
45
  color="secondary"
46
- class="flex justify-between gap-3 border text-start {selected ? 'border-neutral-500!' : ''}"
46
+ class="hover:bg-primary-50 flex justify-between gap-3 border py-4 text-start {selected
47
+ ? 'border-primary/50 bg-primary-50'
48
+ : 'border-light-200 dark:border-light-300'}"
47
49
  >
48
50
  <div class="flex flex-col">
49
- <div class="flex items-center gap-1">
50
- <Text fontWeight="bold">{item.title}</Text>
51
+ <div class="flex place-items-center gap-2">
52
+ <Text fontWeight="semi-bold">{item.title}</Text>
51
53
  <Icon icon={item.icon} size="1.25rem" class={item.iconClass} />
52
54
  </div>
53
55
  {#if item.description}
@@ -58,9 +60,9 @@
58
60
  >
59
61
  {/if}
60
62
  <div class="mt-2">
61
- <Badge size="small" color="primary" shape="rectangle">{item.type}</Badge>
63
+ <Badge color="primary" size="small">{item.type}</Badge>
62
64
  {#if item.isGlobal}
63
- <Badge size="small" shape="rectangle" color="warning">Global</Badge>
65
+ <Badge color="warning" size="small">Global</Badge>
64
66
  {/if}
65
67
  </div>
66
68
  </div>
@@ -4,8 +4,8 @@
4
4
  import ModalBody from '../Modal/ModalBody.svelte';
5
5
  import ModalFooter from '../Modal/ModalFooter.svelte';
6
6
  import HStack from '../Stack/HStack.svelte';
7
- import type { Color } from '../../types.js';
8
7
  import { t } from '../../services/translation.svelte.js';
8
+ import type { Color, ModalSize } from '../../types.js';
9
9
  import type { Snippet } from 'svelte';
10
10
 
11
11
  interface Props {
@@ -15,7 +15,7 @@
15
15
  confirmText?: string;
16
16
  confirmColor?: Color;
17
17
  disabled?: boolean;
18
- size?: 'small' | 'medium';
18
+ size?: ModalSize;
19
19
  onClose: (confirmed: boolean) => void;
20
20
  promptSnippet?: Snippet;
21
21
  }
@@ -1,4 +1,4 @@
1
- import type { Color } from '../../types.js';
1
+ import type { Color, ModalSize } from '../../types.js';
2
2
  import type { Snippet } from 'svelte';
3
3
  interface Props {
4
4
  title?: string;
@@ -7,7 +7,7 @@ interface Props {
7
7
  confirmText?: string;
8
8
  confirmColor?: Color;
9
9
  disabled?: boolean;
10
- size?: 'small' | 'medium';
10
+ size?: ModalSize;
11
11
  onClose: (confirmed: boolean) => void;
12
12
  promptSnippet?: Snippet;
13
13
  }
@@ -3,7 +3,8 @@
3
3
  import Text from '../Text/Text.svelte';
4
4
  import { zIndex } from '../../constants.js';
5
5
  import { styleVariants } from '../../styles.js';
6
- import { MenuItemType, type ContextMenuProps, type ActionItem } from '../../types.js';
6
+ import { type ActionItem, type ContextMenuProps, type MenuItems } from '../../types.js';
7
+ import { isMenuItemType } from '../../utilities/common.js';
7
8
  import { cleanClass, isEnabled } from '../../utilities/internal.js';
8
9
  import { DropdownMenu } from 'bits-ui';
9
10
  import { fly } from 'svelte/transition';
@@ -20,10 +21,6 @@
20
21
  ...restProps
21
22
  }: ContextMenuProps = $props();
22
23
 
23
- const isDivider = (item: ActionItem | MenuItemType): item is MenuItemType => {
24
- return item === MenuItemType.Divider;
25
- };
26
-
27
24
  const itemStyles = tv({
28
25
  base: 'hover:bg-light-200 flex w-full items-center gap-1 rounded-lg p-1 text-start hover:cursor-pointer',
29
26
  variants: {
@@ -83,10 +80,35 @@
83
80
  }
84
81
  };
85
82
 
86
- const filteredItems = $derived(
87
- items.filter((item) => item !== undefined).filter((item) => isDivider(item) || isEnabled(item)),
88
- );
89
- const filteredBottomItems = $derived(bottomItems?.filter((item) => item !== undefined).filter(isEnabled));
83
+ const getFilteredItems = (items?: MenuItems) => {
84
+ if (!items) {
85
+ return [];
86
+ }
87
+
88
+ const results = [];
89
+ for (const item of items) {
90
+ if (item && (isMenuItemType(item) || isEnabled(item))) {
91
+ results.push(item);
92
+ continue;
93
+ }
94
+ }
95
+
96
+ // remove trailing dividers
97
+ for (let i = results.length - 1; i >= 0; i--) {
98
+ const item = results[i];
99
+ if (isMenuItemType(item)) {
100
+ results.pop();
101
+ continue;
102
+ }
103
+
104
+ break;
105
+ }
106
+
107
+ return results;
108
+ };
109
+
110
+ const filteredItems = $derived(getFilteredItems(items));
111
+ const filteredBottomItems = $derived(getFilteredItems(bottomItems) as ActionItem[]);
90
112
 
91
113
  const alignOffset = $derived(target.clientWidth / 2);
92
114
  const sideOffset = $derived(-target.clientHeight / 2);
@@ -100,8 +122,8 @@
100
122
  {#if open}
101
123
  <div {...wrapperProps} class={zIndex.ContextMenu}>
102
124
  <div {...props} {...restProps} class={cleanClass(wrapperStyles({ size }), className)} transition:fly>
103
- {#each filteredItems as item, i (isDivider(item) ? i : item.title)}
104
- {#if isDivider(item)}
125
+ {#each filteredItems as item, i (isMenuItemType(item) ? i : item.title)}
126
+ {#if isMenuItemType(item)}
105
127
  <DropdownMenu.Separator class="dark:border-light-300 my-0.5 border-t" />
106
128
  {:else}
107
129
  <DropdownMenu.Item
@@ -118,7 +140,7 @@
118
140
  {/if}
119
141
  {/each}
120
142
 
121
- {#if filteredBottomItems}
143
+ {#if filteredBottomItems.length > 0}
122
144
  <DropdownMenu.Separator class="dark:border-light-300 my-0.5 border-t" />
123
145
  <div class="flex gap-1 px-1">
124
146
  {#each filteredBottomItems as item (item.title)}
@@ -1,6 +1,7 @@
1
1
  <script lang="ts">
2
2
  import IconButton from '../IconButton/IconButton.svelte';
3
3
  import { menuManager } from '../../services/menu-manager.svelte.js';
4
+ import { t } from '../../services/translation.svelte.js';
4
5
  import type { ContextMenuButtonProps } from '../../types.js';
5
6
  import { mdiDotsVertical } from '@mdi/js';
6
7
 
@@ -10,8 +11,10 @@
10
11
  icon = mdiDotsVertical,
11
12
  variant = 'ghost',
12
13
  shape = 'round',
14
+ 'aria-label': ariaLabel,
13
15
  items,
14
16
  bottomItems,
17
+ translations,
15
18
  ...rest
16
19
  }: ContextMenuButtonProps = $props();
17
20
 
@@ -20,4 +23,12 @@
20
23
  };
21
24
  </script>
22
25
 
23
- <IconButton {icon} {color} {shape} {variant} {...rest} {onclick} />
26
+ <IconButton
27
+ {icon}
28
+ {color}
29
+ {shape}
30
+ {variant}
31
+ aria-label={ariaLabel ?? t('open_menu', translations)}
32
+ {...rest}
33
+ {onclick}
34
+ />
@@ -5,7 +5,7 @@
5
5
  import ModalFooter from '../Modal/ModalFooter.svelte';
6
6
  import HStack from '../Stack/HStack.svelte';
7
7
  import { t } from '../../services/translation.svelte.js';
8
- import type { Color } from '../../types.js';
8
+ import type { Color, ModalSize } from '../../types.js';
9
9
  import { generateId } from '../../utilities/internal.js';
10
10
  import type { Snippet } from 'svelte';
11
11
 
@@ -15,7 +15,7 @@
15
15
  submitText?: string;
16
16
  submitColor?: Color;
17
17
  disabled?: boolean;
18
- size?: 'small' | 'medium';
18
+ size?: ModalSize;
19
19
  preventDefault?: boolean;
20
20
  onClose: () => void;
21
21
  onSubmit: (event: SubmitEvent) => void;
@@ -1,4 +1,4 @@
1
- import type { Color } from '../../types.js';
1
+ import type { Color, ModalSize } from '../../types.js';
2
2
  import type { Snippet } from 'svelte';
3
3
  type Props = {
4
4
  title: string;
@@ -6,7 +6,7 @@ type Props = {
6
6
  submitText?: string;
7
7
  submitColor?: Color;
8
8
  disabled?: boolean;
9
- size?: 'small' | 'medium';
9
+ size?: ModalSize;
10
10
  preventDefault?: boolean;
11
11
  onClose: () => void;
12
12
  onSubmit: (event: SubmitEvent) => void;
@@ -22,7 +22,7 @@
22
22
  const { label, description, readOnly, required, invalid, disabled, ...labelProps } = $derived(getFieldContext());
23
23
 
24
24
  const styles = tv({
25
- base: 'w-full bg-gray-200 outline-none disabled:cursor-not-allowed disabled:bg-gray-300 disabled:text-gray-400 dark:bg-gray-600 dark:disabled:bg-gray-800 dark:disabled:text-gray-200',
25
+ base: 'focus-within:ring-primary dark:focus-within:ring-primary w-full bg-gray-100 ring-1 ring-gray-200 outline-none focus-within:ring-1 disabled:cursor-not-allowed disabled:bg-gray-300 disabled:text-gray-400 dark:bg-gray-800 dark:ring-black dark:disabled:bg-gray-800 dark:disabled:text-gray-200',
26
26
  variants: {
27
27
  shape: styleVariants.shape,
28
28
  padding: {
@@ -27,15 +27,15 @@
27
27
  );
28
28
 
29
29
  const iconStyles = tv({
30
- base: 'h-8 w-8 shrink-0 rounded-xl py-1.75',
30
+ base: 'h-8 w-8 shrink-0 rounded-xl py-1.75 text-white',
31
31
  variants: {
32
32
  color: {
33
- primary: 'bg-primary-100 dark:bg-primary-200 text-primary',
34
- secondary: 'bg-light-200 dark:bg-light-300',
35
- success: 'bg-success-100 dark:bg-success-200 text-success',
36
- info: 'bg-info-200 text-info',
37
- warning: 'bg-warning-200 text-warning',
38
- danger: 'bg-danger-200 text-danger',
33
+ primary: 'bg-primary dark:bg-primary-200',
34
+ secondary: 'bg-dark dark:bg-light-300',
35
+ success: 'bg-success dark:bg-success-200',
36
+ info: 'bg-info dark:text-info-50',
37
+ warning: 'bg-warning dark:text-warning-50',
38
+ danger: 'bg-danger dark:text-danger-50',
39
39
  },
40
40
  },
41
41
  });
@@ -4,7 +4,6 @@
4
4
  import { isCustomToast } from '../../services/toast-manager.svelte.js';
5
5
  import type { ToastPanelProps } from '../../types.js';
6
6
  import { cleanClass } from '../../utilities/internal.js';
7
- import { twMerge } from 'tailwind-merge';
8
7
 
9
8
  const { items, class: className, ...props }: ToastPanelProps = $props();
10
9
 
@@ -12,12 +11,10 @@
12
11
  </script>
13
12
 
14
13
  <div
15
- class={twMerge(
16
- cleanClass(
17
- isEmpty ? 'hidden' : 'absolute top-0 right-0 flex flex-col items-end justify-end gap-2 p-4',
18
- zIndex.ToastPanel,
19
- className,
20
- ),
14
+ class={cleanClass(
15
+ isEmpty ? 'hidden' : 'absolute top-0 right-0 flex flex-col items-end justify-end gap-2 p-4',
16
+ zIndex.ToastPanel,
17
+ className,
21
18
  )}
22
19
  {...props}
23
20
  >
@@ -7,7 +7,6 @@
7
7
  import { cleanClass } from '../utilities/internal.js';
8
8
  import { Button as ButtonPrimitive } from 'bits-ui';
9
9
  import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
10
- import { twMerge } from 'tailwind-merge';
11
10
  import { tv } from 'tailwind-variants';
12
11
 
13
12
  type InternalButtonProps = ButtonProps & {
@@ -99,22 +98,20 @@
99
98
 
100
99
  const classList = $derived(
101
100
  cleanClass(
102
- twMerge(
103
- buttonVariants({
104
- shape,
105
- fullWidth,
106
- textPadding: icon ? undefined : size,
107
- textSize: size,
108
- iconSize: icon ? size : undefined,
109
- disabled,
110
- roundedSize: shape === 'semi-round' ? size : undefined,
111
- filledColor: variant === 'filled' ? color : undefined,
112
- filledColorHover: variant === 'filled' ? color : undefined,
113
- outlineColor: variant === 'outline' ? color : undefined,
114
- ghostColor: variant === 'ghost' ? color : undefined,
115
- }),
116
- className,
117
- ),
101
+ buttonVariants({
102
+ shape,
103
+ fullWidth,
104
+ textPadding: icon ? undefined : size,
105
+ textSize: size,
106
+ iconSize: icon ? size : undefined,
107
+ disabled,
108
+ roundedSize: shape === 'semi-round' ? size : undefined,
109
+ filledColor: variant === 'filled' ? color : undefined,
110
+ filledColorHover: variant === 'filled' ? color : undefined,
111
+ outlineColor: variant === 'outline' ? color : undefined,
112
+ ghostColor: variant === 'ghost' ? color : undefined,
113
+ }),
114
+ className,
118
115
  ),
119
116
  );
120
117
 
@@ -4,16 +4,76 @@ export declare const asText: (...items: unknown[]) => string;
4
4
  declare class CommandPaletteManager {
5
5
  #private;
6
6
  selectedIndex: number;
7
- items: (ActionItem & {
7
+ items: ({
8
+ title: string;
9
+ description?: string;
10
+ type?: string;
11
+ searchText?: string;
12
+ icon: import("../types.js").IconLike;
13
+ iconClass?: string;
14
+ color?: import("../types.js").Color;
15
+ onAction: import("../types.js").ActionItemHandler;
16
+ shortcuts?: MaybeArray<import("../actions/shortcut.js").Shortcut>;
17
+ shortcutOptions?: {
18
+ ignoreInputFields?: boolean;
19
+ preventDefault?: boolean;
20
+ };
21
+ isGlobal?: boolean;
22
+ } & import("../types.js").IfLike & {
8
23
  id: string;
9
24
  })[];
10
- filteredItems: (ActionItem & {
25
+ filteredItems: ({
26
+ title: string;
27
+ description?: string;
28
+ type?: string;
29
+ searchText?: string;
30
+ icon: import("../types.js").IconLike;
31
+ iconClass?: string;
32
+ color?: import("../types.js").Color;
33
+ onAction: import("../types.js").ActionItemHandler;
34
+ shortcuts?: MaybeArray<import("../actions/shortcut.js").Shortcut>;
35
+ shortcutOptions?: {
36
+ ignoreInputFields?: boolean;
37
+ preventDefault?: boolean;
38
+ };
39
+ isGlobal?: boolean;
40
+ } & import("../types.js").IfLike & {
11
41
  id: string;
12
42
  })[];
13
- recentItems: (ActionItem & {
43
+ recentItems: ({
44
+ title: string;
45
+ description?: string;
46
+ type?: string;
47
+ searchText?: string;
48
+ icon: import("../types.js").IconLike;
49
+ iconClass?: string;
50
+ color?: import("../types.js").Color;
51
+ onAction: import("../types.js").ActionItemHandler;
52
+ shortcuts?: MaybeArray<import("../actions/shortcut.js").Shortcut>;
53
+ shortcutOptions?: {
54
+ ignoreInputFields?: boolean;
55
+ preventDefault?: boolean;
56
+ };
57
+ isGlobal?: boolean;
58
+ } & import("../types.js").IfLike & {
14
59
  id: string;
15
60
  })[];
16
- results: (ActionItem & {
61
+ results: ({
62
+ title: string;
63
+ description?: string;
64
+ type?: string;
65
+ searchText?: string;
66
+ icon: import("../types.js").IconLike;
67
+ iconClass?: string;
68
+ color?: import("../types.js").Color;
69
+ onAction: import("../types.js").ActionItemHandler;
70
+ shortcuts?: MaybeArray<import("../actions/shortcut.js").Shortcut>;
71
+ shortcutOptions?: {
72
+ ignoreInputFields?: boolean;
73
+ preventDefault?: boolean;
74
+ };
75
+ isGlobal?: boolean;
76
+ } & import("../types.js").IfLike & {
17
77
  id: string;
18
78
  })[];
19
79
  get isEnabled(): boolean;
@@ -12,6 +12,7 @@ declare const defaultTranslations: {
12
12
  show_password: string;
13
13
  hide_password: string;
14
14
  dark_theme: string;
15
+ open_menu: string;
15
16
  command_palette_prompt_default: string;
16
17
  command_palette_to_select: string;
17
18
  command_palette_to_navigate: string;
@@ -17,6 +17,8 @@ const defaultTranslations = {
17
17
  hide_password: 'Hide password',
18
18
  // theme switcher
19
19
  dark_theme: 'Toggle dark theme',
20
+ // context menu
21
+ open_menu: 'Open menu',
20
22
  // command palette
21
23
  command_palette_prompt_default: 'Quickly find pages, actions, or commands',
22
24
  command_palette_to_select: 'to select',
@@ -119,7 +119,7 @@
119
119
  --immich-ui-primary-900: oklch(0.211 0.068 273.01);
120
120
  --immich-ui-primary-950: oklch(0.15 0.05 273.01);
121
121
 
122
- --immich-ui-success-50: oklch(0.969 0.051 148.59);
122
+ --immich-ui-success-50: oklch(0.983 0.0115 162.06);
123
123
  --immich-ui-success-100: oklch(0.937 0.109 148.66);
124
124
  --immich-ui-success-200: oklch(0.863 0.249 147.5);
125
125
  --immich-ui-success-300: oklch(0.813 0.234 147.54);
@@ -131,7 +131,7 @@
131
131
  --immich-ui-success-900: oklch(0.252 0.073 147.47);
132
132
  --immich-ui-success-950: oklch(0.194 0.056 147.87);
133
133
 
134
- --immich-ui-danger-50: oklch(0.96 0.018 17.57);
134
+ --immich-ui-danger-50: oklch(0.9747 0.0109 24.32);
135
135
  --immich-ui-danger-100: oklch(0.926 0.034 17.84);
136
136
  --immich-ui-danger-200: oklch(0.853 0.074 19.77);
137
137
  --immich-ui-danger-300: oklch(0.774 0.125 21.53);
@@ -143,7 +143,7 @@
143
143
  --immich-ui-danger-900: oklch(0.245 0.094 29.51);
144
144
  --immich-ui-danger-950: oklch(0.186 0.07 29.56);
145
145
 
146
- --immich-ui-warning: oklch(0.812 0.17 76.3);
146
+ --immich-ui-warning: oklch(0.9887 0.0126 86.83);
147
147
  --immich-ui-warning-50: oklch(0.983 0.01 58.27);
148
148
  --immich-ui-warning-100: oklch(0.959 0.027 63.96);
149
149
  --immich-ui-warning-200: oklch(0.925 0.051 64.24);
@@ -156,7 +156,7 @@
156
156
  --immich-ui-warning-900: oklch(0.27 0.057 76.33);
157
157
  --immich-ui-warning-950: oklch(0.209 0.044 77.51);
158
158
 
159
- --immich-ui-info-50: oklch(0.958 0.02 267.31);
159
+ --immich-ui-info-50: oklch(0.9801 0.0096 252.81);
160
160
  --immich-ui-info-100: oklch(0.922 0.036 268.3);
161
161
  --immich-ui-info-200: oklch(0.845 0.073 265.19);
162
162
  --immich-ui-info-300: oklch(0.769 0.114 261.53);
package/dist/types.d.ts CHANGED
@@ -1,8 +1,8 @@
1
+ import type { Shortcut } from './actions/shortcut.js';
1
2
  import type { Translations } from './services/translation.svelte.js';
2
3
  import type { DateTime } from 'luxon';
3
4
  import type { Component, Snippet } from 'svelte';
4
5
  import type { HTMLAnchorAttributes, HTMLAttributes, HTMLButtonAttributes, HTMLInputAttributes, HTMLLabelAttributes, HTMLTextareaAttributes } from 'svelte/elements';
5
- import type { Shortcut } from './actions/shortcut.js';
6
6
  export type Color = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info';
7
7
  export type TextColor = Color | 'muted';
8
8
  export type TextVariant = 'italic';
@@ -71,9 +71,9 @@ export type CloseButtonProps = {
71
71
  export type ContextMenuButtonProps = ButtonBase & {
72
72
  icon?: IconLike;
73
73
  position?: ContextMenuPosition;
74
- 'aria-label': string;
75
74
  items: MenuItems;
76
75
  bottomItems?: Array<ActionItem | undefined>;
76
+ translations?: TranslationProps<'open_menu'>;
77
77
  } & Omit<HTMLButtonAttributes, 'color' | 'size'>;
78
78
  export type IconButtonProps = ButtonBase & {
79
79
  icon: IconLike;
@@ -243,8 +243,8 @@ export type DatePickerProps = {
243
243
  export type IfLike = {
244
244
  $if?: () => boolean;
245
245
  };
246
- export type ActionItemHandler<T = never> = (item: ActionItem<T>) => void | Promise<void>;
247
- export type ActionItem<T = never> = Omit<{
246
+ export type ActionItemHandler<T extends ActionItem = ActionItem> = (item: T) => void | Promise<void>;
247
+ export type ActionItem = {
248
248
  title: string;
249
249
  description?: string;
250
250
  type?: string;
@@ -252,17 +252,14 @@ export type ActionItem<T = never> = Omit<{
252
252
  icon: IconLike;
253
253
  iconClass?: string;
254
254
  color?: Color;
255
- onAction: ActionItemHandler<T>;
256
- data: T;
255
+ onAction: ActionItemHandler;
257
256
  shortcuts?: MaybeArray<Shortcut>;
258
257
  shortcutOptions?: {
259
258
  ignoreInputFields?: boolean;
260
259
  preventDefault?: boolean;
261
260
  };
262
261
  isGlobal?: boolean;
263
- } & IfLike, [
264
- T
265
- ] extends [never] ? 'data' : ''>;
262
+ } & IfLike;
266
263
  export type BreadcrumbsProps = {
267
264
  separator?: IconLike | {
268
265
  text: string;
@@ -1,3 +1,4 @@
1
+ import { MenuItemType, type ActionItem } from '../types.js';
1
2
  import type { DateTime } from 'luxon';
2
3
  export declare const resolveUrl: (url: string, currentHostname?: string) => string;
3
4
  export declare const isExternalLink: (href: string) => boolean;
@@ -14,6 +15,7 @@ export type ArticleMetadata = {
14
15
  section?: string;
15
16
  tags?: string[];
16
17
  };
18
+ export declare const isMenuItemType: (item: ActionItem | MenuItemType) => item is MenuItemType;
17
19
  export declare const resolveMetadata: (site: Metadata, page?: Metadata, article?: ArticleMetadata) => {
18
20
  type: string;
19
21
  siteName: string;
@@ -1,4 +1,5 @@
1
1
  import { env } from '$env/dynamic/public';
2
+ import { MenuItemType } from '../types.js';
2
3
  const getImmichApp = (host) => {
3
4
  if (!host || !host.endsWith('immich.app')) {
4
5
  return false;
@@ -25,6 +26,9 @@ export const resolveUrl = (url, currentHostname) => {
25
26
  export const isExternalLink = (href) => {
26
27
  return !(href.startsWith('/') || href.startsWith('#'));
27
28
  };
29
+ export const isMenuItemType = (item) => {
30
+ return item === MenuItemType.Divider;
31
+ };
28
32
  export const resolveMetadata = (site, page, article) => {
29
33
  const title = page ? `${page.title} | ${site.title}` : site.title;
30
34
  const description = page?.description ?? site.description;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immich/ui",
3
- "version": "0.49.3",
3
+ "version": "0.50.1",
4
4
  "license": "GNU Affero General Public License version 3",
5
5
  "repository": {
6
6
  "type": "git",