@immich/ui 0.46.0 → 0.48.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.
@@ -29,13 +29,13 @@
29
29
  false: '',
30
30
  },
31
31
  color: {
32
- primary: 'bg-primary-100 text-primary dark:bg-primary-900',
33
- secondary: 'text-dark dark:bg-dark/25 bg-gray-200',
32
+ primary: 'bg-primary-100 text-primary',
33
+ secondary: 'text-dark bg-light-200 dark:bg-light-300',
34
34
  muted: 'bg-subtle text-subtle dark:bg-subtle',
35
- info: 'bg-info-100 text-info dark:bg-info-900',
36
- warning: 'bg-warning-100 text-warning dark:bg-warning-900',
37
- danger: 'bg-danger-100 text-danger dark:bg-danger-900',
38
- success: 'bg-success-100 text-success dark:bg-success-900',
35
+ info: 'bg-info-100 text-info',
36
+ warning: 'bg-warning-100 text-warning',
37
+ danger: 'bg-danger-100 text-danger',
38
+ success: 'bg-success-100 text-success',
39
39
  },
40
40
  },
41
41
  });
@@ -28,17 +28,12 @@
28
28
  variants: {
29
29
  shape: styleVariants.shape,
30
30
  color: {
31
- primary:
32
- 'bg-primary-100 border-primary-100 text-primary-950 dark:text-primary-50 dark:bg-primary-800 dark:border-primary-800',
33
- secondary:
34
- 'border-neutral-20 bg-neutral-100 text-neutral-950 dark:border-neutral-700 dark:bg-neutral-800 dark:text-neutral-50',
35
- success:
36
- 'border-success-100 bg-success-100 text-success-950 dark:text-success-50 dark:bg-success-800 dark:border-success-800',
37
- info: 'border-info-100 bg-info-200 text-info-950 dark:text-info-50 dark:bg-info-800 dark:border-info-800',
38
- warning:
39
- 'border-warning-100 bg-warning-200 text-warning-950 dark:text-warning-50 dark:bg-warning-800 dark:border-warning-800',
40
- danger:
41
- 'border-danger-100 bg-danger-200 text-danger-950 dark:text-danger-50 dark:bg-danger-900 dark:border-danger-800',
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',
42
37
  },
43
38
  textSize: styleVariants.textSize,
44
39
  paddingSize: {
@@ -51,12 +51,12 @@
51
51
  base: 'flex w-full grow flex-col',
52
52
  variants: {
53
53
  color: {
54
- primary: 'bg-primary-50 dark:bg-primary-900',
55
- secondary: 'text-dark bg-neutral-50 dark:bg-neutral-900 dark:text-white',
56
- success: 'bg-success-50 dark:bg-success-900',
57
- danger: 'bg-danger-50 dark:bg-danger-900',
58
- warning: 'bg-warning-50 dark:bg-warning-900',
59
- info: 'bg-info-50 dark:bg-info-900',
54
+ primary: 'bg-primary-50 dark:bg-primary-100',
55
+ secondary: 'text-dark bg-light-50 dark:bg-light-100 dark:text-white',
56
+ success: 'bg-success-50 dark:bg-success-100',
57
+ danger: 'bg-danger-50 dark:bg-danger-100',
58
+ warning: 'bg-warning-50 dark:bg-warning-100',
59
+ info: 'bg-info-50 dark:bg-info-100',
60
60
  },
61
61
  },
62
62
  });
@@ -41,7 +41,7 @@
41
41
  false: '',
42
42
  muted: 'text-dark bg-subtle',
43
43
  primary: 'text-dark dark:bg-primary/20 bg-gray-200 dark:text-gray-200',
44
- secondary: 'text-light bg-gray-700 dark:bg-gray-200',
44
+ secondary: 'text-light bg-light-700',
45
45
  success: 'bg-success text-light',
46
46
  danger: 'bg-danger text-light',
47
47
  warning: 'bg-warning text-light',
@@ -50,7 +50,7 @@
50
50
 
51
51
  outlineTheme: {
52
52
  false: '',
53
- muted: 'border border-gray-600 text-gray-600 dark:border-gray-400 dark:text-gray-400',
53
+ muted: 'border-light-600 text-light-600 border',
54
54
  primary: 'border-primary text-primary border',
55
55
  secondary: 'border-dark text-dark border',
56
56
  success: 'border-success text-success border',
@@ -1,13 +1,14 @@
1
1
  <script lang="ts">
2
2
  import { renderShortcut } from '../../actions/shortcut.js';
3
+ import Badge from '../Badge/Badge.svelte';
3
4
  import Button from '../Button/Button.svelte';
4
5
  import Icon from '../Icon/Icon.svelte';
5
6
  import IconButton from '../IconButton/IconButton.svelte';
6
7
  import Kbd from '../Kbd/Kbd.svelte';
7
8
  import Text from '../Text/Text.svelte';
8
9
  import type { CommandItemResponse } from '../../services/command-palette-manager.svelte';
10
+ import { cleanClass } from '../../utilities/internal.js';
9
11
  import { mdiClose } from '@mdi/js';
10
- import Badge from '../Badge/Badge.svelte';
11
12
 
12
13
  type Props = {
13
14
  item: CommandItemResponse;
@@ -78,9 +79,7 @@
78
79
  <div class="flex shrink-0 flex-col justify-end gap-1">
79
80
  {#each renderedShortcuts as shortcut (shortcut.join('-'))}
80
81
  <div class="flex justify-end">
81
- <Kbd size="tiny" class={selected ? '' : 'border-neutral-200 dark:border-neutral-700'}
82
- >{shortcut.join(' ')}</Kbd
83
- >
82
+ <Kbd size="tiny" class={cleanClass(selected && 'border')}>{shortcut.join(' ')}</Kbd>
84
83
  </div>
85
84
  {/each}
86
85
  </div>
@@ -3,7 +3,7 @@
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 MenuItem } from '../../types.js';
6
+ import { MenuItemType, type ContextMenuProps, type ActionItem } from '../../types.js';
7
7
  import { cleanClass, isEnabled } from '../../utilities/internal.js';
8
8
  import { DropdownMenu } from 'bits-ui';
9
9
  import { fly } from 'svelte/transition';
@@ -20,19 +20,19 @@
20
20
  ...restProps
21
21
  }: ContextMenuProps = $props();
22
22
 
23
- const isDivider = (item: MenuItem | MenuItemType): item is MenuItemType => {
23
+ const isDivider = (item: ActionItem | MenuItemType): item is MenuItemType => {
24
24
  return item === MenuItemType.Divider;
25
25
  };
26
26
 
27
27
  const itemStyles = tv({
28
- base: 'dark:hover:bg-primary-800 hover:bg-primary-100 flex w-full items-center gap-1 rounded-lg p-1 text-start hover:cursor-pointer',
28
+ base: 'hover:bg-light-200 flex w-full items-center gap-1 rounded-lg p-1 text-start hover:cursor-pointer',
29
29
  variants: {
30
30
  color: styleVariants.textColor,
31
31
  },
32
32
  });
33
33
 
34
34
  const wrapperStyles = tv({
35
- base: 'flex flex-col gap-1 overflow-hidden rounded-xl border bg-neutral-100 py-1 shadow-sm dark:border-neutral-700 dark:bg-neutral-900',
35
+ base: 'bg-light-100 dark:border-light-300 flex flex-col gap-1 overflow-hidden rounded-xl border py-1 shadow-sm',
36
36
  variants: {
37
37
  size: {
38
38
  tiny: 'w-32',
@@ -102,12 +102,12 @@
102
102
  <div {...props} {...restProps} class={cleanClass(wrapperStyles({ size }), className)} transition:fly>
103
103
  {#each filteredItems as item, i (isDivider(item) ? i : item.title)}
104
104
  {#if isDivider(item)}
105
- <DropdownMenu.Separator class="my-0.5 border-t dark:border-neutral-700" />
105
+ <DropdownMenu.Separator class="dark:border-light-300 my-0.5 border-t" />
106
106
  {:else}
107
107
  <DropdownMenu.Item
108
108
  textValue={item.title}
109
109
  closeOnSelect
110
- onSelect={(event) => item.onSelect?.({ event, item })}
110
+ onSelect={() => item.onAction(item)}
111
111
  class="px-1"
112
112
  >
113
113
  <div class={itemStyles({ color: item.color })}>
@@ -119,13 +119,13 @@
119
119
  {/each}
120
120
 
121
121
  {#if filteredBottomItems}
122
- <DropdownMenu.Separator class="my-0.5 border-t dark:border-neutral-700" />
122
+ <DropdownMenu.Separator class="dark:border-light-300 my-0.5 border-t" />
123
123
  <div class="flex gap-1 px-1">
124
124
  {#each filteredBottomItems as item (item.title)}
125
125
  <DropdownMenu.Item
126
126
  textValue={item.title}
127
127
  closeOnSelect
128
- onSelect={(event) => item.onSelect?.({ event, item })}
128
+ onSelect={() => item.onAction(item)}
129
129
  title={item.title}
130
130
  >
131
131
  <div class={cleanClass(itemStyles({ color: item.color }))}>
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import IconButton from '../IconButton/IconButton.svelte';
3
+ import { menuManager } from '../../services/menu-manager.svelte.js';
4
+ import type { ContextMenuButtonProps } from '../../types.js';
5
+ import { mdiDotsVertical } from '@mdi/js';
6
+
7
+ const {
8
+ color = 'secondary',
9
+ position = 'top-right',
10
+ icon = mdiDotsVertical,
11
+ variant = 'ghost',
12
+ shape = 'round',
13
+ items,
14
+ bottomItems,
15
+ ...rest
16
+ }: ContextMenuButtonProps = $props();
17
+
18
+ const onclick = async (event: Event) => {
19
+ await menuManager.show({ target: event.currentTarget as HTMLElement, position, items, bottomItems });
20
+ };
21
+ </script>
22
+
23
+ <IconButton {icon} {color} {shape} {variant} {...rest} {onclick} />
@@ -0,0 +1,4 @@
1
+ import type { ContextMenuButtonProps } from '../../types.js';
2
+ declare const ContextMenuButton: import("svelte").Component<ContextMenuButtonProps, {}, "">;
3
+ type ContextMenuButton = ReturnType<typeof ContextMenuButton>;
4
+ export default ContextMenuButton;
@@ -0,0 +1,59 @@
1
+ <script lang="ts">
2
+ import { styleVariants } from '../../styles.js';
3
+ import type { Color, Shape, Size } from '../../types.js';
4
+ import { cleanClass } from '../../utilities/internal.js';
5
+ import type { Snippet } from 'svelte';
6
+ import { tv } from 'tailwind-variants';
7
+
8
+ type Props = {
9
+ progress: number;
10
+ size?: Size;
11
+ shape?: Shape;
12
+ color?: Color;
13
+ class?: string;
14
+ children?: Snippet;
15
+ };
16
+
17
+ let { progress, shape = 'round', size = 'medium', color = 'primary', class: className, children }: Props = $props();
18
+
19
+ const containerStyles = tv({
20
+ base: 'bg-light-100 dark:bg-light-200 dark:border-light-300 relative w-full overflow-hidden border',
21
+ variants: {
22
+ shape: styleVariants.shape,
23
+ size: {
24
+ tiny: 'h-3',
25
+ small: 'h-4',
26
+ medium: 'h-5',
27
+ large: 'h-6',
28
+ giant: 'h-12',
29
+ },
30
+ roundedSize: {
31
+ tiny: 'rounded-sm',
32
+ small: 'rounded-md',
33
+ medium: 'rounded-md',
34
+ large: 'rounded-lg',
35
+ giant: 'rounded-xl',
36
+ },
37
+ },
38
+ });
39
+
40
+ const barStyles = tv({
41
+ base: 'h-full transition-all duration-700 ease-in-out',
42
+ variants: {
43
+ color: styleVariants.filledColor,
44
+ shape: styleVariants.shape,
45
+ },
46
+ });
47
+ </script>
48
+
49
+ <div
50
+ class={cleanClass(
51
+ containerStyles({ size, shape, roundedSize: shape === 'semi-round' ? size : undefined }),
52
+ className,
53
+ )}
54
+ >
55
+ <div class="absolute flex h-full w-full items-center justify-center">
56
+ {@render children?.()}
57
+ </div>
58
+ <div class={cleanClass(barStyles({ color, shape }))} style="width: {progress * 100}%"></div>
59
+ </div>
@@ -0,0 +1,13 @@
1
+ import type { Color, Shape, Size } from '../../types.js';
2
+ import type { Snippet } from 'svelte';
3
+ type Props = {
4
+ progress: number;
5
+ size?: Size;
6
+ shape?: Shape;
7
+ color?: Color;
8
+ class?: string;
9
+ children?: Snippet;
10
+ };
11
+ declare const ProgressBar: import("svelte").Component<Props, {}, "">;
12
+ type ProgressBar = ReturnType<typeof ProgressBar>;
13
+ export default ProgressBar;
@@ -41,13 +41,13 @@
41
41
  base: 'h-8 w-13 rounded-full border-2',
42
42
  variants: {
43
43
  fillColor: {
44
- default: 'border-gray-400 bg-gray-200 dark:border-gray-500 dark:bg-gray-800',
45
- primary: 'bg-primary-100 dark:bg-primary-800 border-transparent',
46
- secondary: 'border-transparent bg-neutral-200 dark:bg-neutral-700',
47
- success: 'bg-success-100 dark:bg-success-800 border-transparent',
48
- danger: 'bg-danger-100 dark:bg-danger-800 border-transparent',
49
- warning: 'bg-warning-100 dark:bg-warning-800 border-transparent',
50
- info: 'bg-info-100 dark:bg-info-800 border-transparent',
44
+ default: 'border-light-400 bg-light-200 dark:border-gray-500',
45
+ primary: 'bg-primary-100 dark:bg-primary-200 border-transparent',
46
+ secondary: 'bg-light-200 dark:bg-light-300 border-transparent',
47
+ success: 'bg-success-100 dark:bg-success-200 border-transparent',
48
+ danger: 'bg-danger-100 dark:bg-danger-200 border-transparent',
49
+ warning: 'bg-warning-100 dark:bg-warning-200 border-transparent',
50
+ info: 'bg-info-100 dark:bg-info-200 border-transparent',
51
51
  },
52
52
  },
53
53
  });
@@ -18,12 +18,12 @@
18
18
  base: 'bg-light text-dark overflow-hidden border py-1.5 shadow-xs transition-all',
19
19
  variants: {
20
20
  color: {
21
- primary: 'border-primary-100 bg-primary-50 dark:bg-primary-900 dark:border-primary-800',
22
- secondary: 'border-neutral-200 bg-neutral-50 dark:border-neutral-700 dark:bg-neutral-800',
23
- success: 'border-success-100 bg-success-50 dark:bg-success-900 dark:border-success-800',
24
- info: 'border-info-100 bg-info-50 dark:bg-info-900 dark:border-info-800',
25
- warning: 'border-warning-100 bg-warning-50 dark:bg-warning-900 dark:border-warning-800',
26
- danger: 'border-danger-100 bg-danger-50 dark:bg-danger-900 dark:border-danger-800',
21
+ primary: 'border-primary-100 bg-primary-50 dark:bg-primary-100 dark:border-primary-200',
22
+ secondary: 'border-light-200 bg-light-100 dark:bg-light-200 dark:border-light-300',
23
+ success: 'border-success-100 bg-success-50 dark:bg-success-100 dark:border-success-200',
24
+ info: 'border-info-100 bg-info-50 dark:bg-info-100 dark:border-info-200',
25
+ warning: 'border-warning-100 bg-warning-50 dark:bg-warning-100 dark:border-warning-200',
26
+ danger: 'border-danger-100 bg-danger-50 dark:bg-danger-100 dark:border-danger-200',
27
27
  },
28
28
  shape: styleVariants.shape,
29
29
  size: {
@@ -30,12 +30,12 @@
30
30
  base: 'h-8 w-8 shrink-0 rounded-xl py-1.75',
31
31
  variants: {
32
32
  color: {
33
- primary: 'bg-primary-100 dark:bg-primary-800 text-primary',
34
- secondary: 'bg-neutral-200 dark:bg-neutral-700',
35
- success: 'bg-success-100 dark:bg-success-800 text-success',
36
- info: 'bg-info-200 dark:bg-info-800 text-info',
37
- warning: 'bg-warning-200 dark:bg-warning-700 text-warning',
38
- danger: 'bg-danger-200 dark:bg-danger-800 text-danger',
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',
39
39
  },
40
40
  },
41
41
  });
package/dist/index.d.ts CHANGED
@@ -55,6 +55,7 @@ export { default as NavbarGroup } from './components/Navbar/NavbarGroup.svelte';
55
55
  export { default as NavbarItem } from './components/Navbar/NavbarItem.svelte';
56
56
  export { default as NumberInput } from './components/NumberInput/NumberInput.svelte';
57
57
  export { default as PasswordInput } from './components/PasswordInput/PasswordInput.svelte';
58
+ export { default as ProgressBar } from './components/ProgressBar/ProgressBar.svelte';
58
59
  export { default as Scrollable } from './components/Scrollable/Scrollable.svelte';
59
60
  export { default as Select } from './components/Select/Select.svelte';
60
61
  export { default as SiteMetadata } from './components/SiteMetadata/SiteMetadata.svelte';
package/dist/index.js CHANGED
@@ -57,6 +57,7 @@ export { default as NavbarGroup } from './components/Navbar/NavbarGroup.svelte';
57
57
  export { default as NavbarItem } from './components/Navbar/NavbarItem.svelte';
58
58
  export { default as NumberInput } from './components/NumberInput/NumberInput.svelte';
59
59
  export { default as PasswordInput } from './components/PasswordInput/PasswordInput.svelte';
60
+ export { default as ProgressBar } from './components/ProgressBar/ProgressBar.svelte';
60
61
  export { default as Scrollable } from './components/Scrollable/Scrollable.svelte';
61
62
  export { default as Select } from './components/Select/Select.svelte';
62
63
  export { default as SiteMetadata } from './components/SiteMetadata/SiteMetadata.svelte';
@@ -72,19 +72,19 @@
72
72
  filledColorHover: styleVariants.filledColorHover,
73
73
  outlineColor: {
74
74
  primary: 'border-primary bg-primary/10 text-primary hover:bg-primary/20 border',
75
- secondary: 'border-dark bg-dark/10 text-dark hover:bg-dark/20 border',
75
+ secondary: 'border-dark bg-light-100 text-dark hover:bg-light-200 border',
76
76
  success: 'border-success bg-success/10 text-success hover:bg-success/20 border',
77
77
  danger: 'border-danger bg-danger/10 text-danger hover:bg-danger/20 border',
78
78
  warning: 'border-warning bg-warning/10 text-warning hover:bg-warning/20 border',
79
79
  info: 'border-info bg-info/10 text-info hover:bg-info/20 border',
80
80
  },
81
81
  ghostColor: {
82
- primary: 'text-primary hover:bg-primary-50 dark:hover:bg-primary-950',
83
- secondary: 'text-dark hover:bg-dark/10',
84
- success: 'text-success hover:bg-success-50 dark:hover:bg-success-950',
85
- danger: 'text-danger hover:bg-danger-50 dark:hover:bg-danger-950',
86
- warning: 'text-warning hover:bg-warning-50 dark:hover:bg-warning-950',
87
- info: 'text-info hover:bg-info-50 dark:hover:bg-info-950',
82
+ primary: 'text-primary hover:bg-primary-50',
83
+ secondary: 'text-dark hover:bg-light-100',
84
+ success: 'text-success hover:bg-success-50',
85
+ danger: 'text-danger hover:bg-danger-50',
86
+ warning: 'text-warning hover:bg-warning-50',
87
+ info: 'text-info hover:bg-info-50',
88
88
  },
89
89
  },
90
90
  });
@@ -51,11 +51,11 @@
51
51
  });
52
52
 
53
53
  const buttonStyles = tv({
54
- base: 'flex h-10 w-10 items-center justify-center rounded-lg hover:cursor-pointer hover:bg-neutral-200 hover:dark:bg-neutral-700',
54
+ base: 'hover:bg-light-200 hover:dark:bg-light-300 flex h-10 w-10 items-center justify-center rounded-lg hover:cursor-pointer',
55
55
  });
56
56
 
57
57
  const segmentStyles = tv({
58
- base: 'rounded px-1 py-0.5 tabular-nums outline-none focus:bg-gray-300 focus:text-gray-900 data-disabled:cursor-not-allowed data-focused:bg-gray-300 data-focused:text-gray-900 data-placeholder:text-gray-400 dark:focus:bg-gray-700 dark:focus:text-gray-100 dark:data-focused:bg-gray-700 dark:data-focused:text-gray-100',
58
+ base: 'focus:bg-light-300 focus:text-light-900 data-focused:bg-light-300 data-focused:text-light-900 data-placeholder:text-light-400 dark:focus:bg-light-700 dark:focus:text-light-100 dark:data-focused:bg-light-300 dark:data-focused:text-light-900 rounded px-1 py-0.5 tabular-nums outline-none data-disabled:cursor-not-allowed',
59
59
  variants: {
60
60
  textSize: styleVariants.textSize,
61
61
  },
@@ -140,7 +140,7 @@
140
140
  {#each weekDates as date (`date-${date.toString()}`)}
141
141
  <DatePicker.Cell {date} month={month.value} class="flex-1">
142
142
  <DatePicker.Day
143
- class="{buttonStyles()} data-selected:bg-primary data-selected:hover:bg-primary-300 data-selected:text-light data-today:border-primary-200 data-today:dark:border-primary-600 data-today:dark:bg-primary-800 data-today:bg-primary-50 border border-transparent text-sm data-disabled:cursor-not-allowed data-disabled:opacity-40 data-outside-month:text-gray-400 data-unavailable:cursor-not-allowed data-unavailable:text-gray-300 data-unavailable:line-through"
143
+ class="{buttonStyles()} data-selected:bg-primary data-selected:hover:bg-primary-300 data-selected:text-light data-today:border-primary-200 data-today:dark:border-primary-400 data-today:dark:bg-primary-200 data-today:bg-primary-50 data-outside-month:text-light-400 data-unavailable:text-light-300 border border-transparent text-sm data-disabled:cursor-not-allowed data-disabled:opacity-40 data-unavailable:cursor-not-allowed data-unavailable:line-through"
144
144
  >
145
145
  {date.day}
146
146
  </DatePicker.Day>
@@ -66,15 +66,39 @@
66
66
  --color-info-900: var(--immich-ui-info-900);
67
67
  --color-info-950: var(--immich-ui-info-950);
68
68
 
69
+ --color-light-50: var(--immich-ui-light-50);
70
+ --color-light-100: var(--immich-ui-light-100);
71
+ --color-light-200: var(--immich-ui-light-200);
72
+ --color-light-300: var(--immich-ui-light-300);
73
+ --color-light-400: var(--immich-ui-light-400);
74
+ --color-light-500: var(--immich-ui-light-500);
75
+ --color-light-600: var(--immich-ui-light-600);
76
+ --color-light-700: var(--immich-ui-light-700);
77
+ --color-light-800: var(--immich-ui-light-800);
78
+ --color-light-900: var(--immich-ui-light-900);
79
+ --color-light-950: var(--immich-ui-light-950);
80
+
81
+ --color-dark-50: var(--immich-ui-dark-50);
82
+ --color-dark-100: var(--immich-ui-dark-100);
83
+ --color-dark-200: var(--immich-ui-dark-200);
84
+ --color-dark-300: var(--immich-ui-dark-300);
85
+ --color-dark-400: var(--immich-ui-dark-400);
86
+ --color-dark-500: var(--immich-ui-dark-500);
87
+ --color-dark-600: var(--immich-ui-dark-600);
88
+ --color-dark-700: var(--immich-ui-dark-700);
89
+ --color-dark-800: var(--immich-ui-dark-800);
90
+ --color-dark-900: var(--immich-ui-dark-900);
91
+ --color-dark-950: var(--immich-ui-dark-950);
92
+
69
93
  --color-primary: var(--immich-ui-primary-500);
70
94
  --color-success: var(--immich-ui-success-500);
71
95
  --color-danger: var(--immich-ui-danger-500);
72
96
  --color-warning: var(--immich-ui-warning-500);
73
97
  --color-info: var(--immich-ui-info-500);
74
- --color-light: rgb(var(--immich-ui-light));
98
+ --color-light: var(--immich-ui-light);
75
99
  --color-dark: var(--immich-ui-dark);
76
- --color-muted: rgb(var(--immich-ui-muted));
77
- --color-subtle: rgb(var(--immich-ui-gray));
100
+ --color-muted: var(--immich-ui-muted);
101
+ --color-subtle: var(--immich-ui-gray);
78
102
  }
79
103
 
80
104
  @custom-variant dark (&:where(.dark, .dark *):not(.light));
@@ -144,79 +168,127 @@
144
168
  --immich-ui-info-900: oklch(0.234 0.067 252.62);
145
169
  --immich-ui-info-950: oklch(0.178 0.052 253.61);
146
170
 
147
- --immich-ui-light: 255 255 255;
148
- --immich-ui-dark: rgb(63, 60, 60);
149
- --immich-ui-muted: 161 161 161;
150
- --immich-ui-gray: 246 246 246;
151
- --immich-ui-default-border: 209 213 219;
171
+ --immich-ui-light-50: var(--color-neutral-50);
172
+ --immich-ui-light-100: var(--color-neutral-100);
173
+ --immich-ui-light-200: var(--color-neutral-200);
174
+ --immich-ui-light-300: var(--color-neutral-300);
175
+ --immich-ui-light-400: var(--color-neutral-400);
176
+ --immich-ui-light-500: var(--color-neutral-500);
177
+ --immich-ui-light-600: var(--color-neutral-600);
178
+ --immich-ui-light-700: var(--color-neutral-700);
179
+ --immich-ui-light-800: var(--color-neutral-800);
180
+ --immich-ui-light-900: var(--color-neutral-900);
181
+ --immich-ui-light-950: var(--color-neutral-950);
182
+
183
+ --immich-ui-dark-50: var(--color-neutral-950);
184
+ --immich-ui-dark-100: var(--color-neutral-900);
185
+ --immich-ui-dark-200: var(--color-neutral-800);
186
+ --immich-ui-dark-300: var(--color-neutral-700);
187
+ --immich-ui-dark-400: var(--color-neutral-600);
188
+ --immich-ui-dark-500: var(--color-neutral-500);
189
+ --immich-ui-dark-600: var(--color-neutral-400);
190
+ --immich-ui-dark-700: var(--color-neutral-300);
191
+ --immich-ui-dark-800: var(--color-neutral-200);
192
+ --immich-ui-dark-900: var(--color-neutral-100);
193
+ --immich-ui-dark-950: var(--color-neutral-50);
194
+
195
+ --immich-ui-light: oklch(100% 0 0);
196
+ --immich-ui-dark: oklch(36% 0 17);
197
+ --immich-ui-muted: oklch(71% 0 271);
198
+ --immich-ui-gray: oklch(97% 0 271);
199
+ --immich-ui-default-border: var(--immich-ui-light-300);
152
200
  }
153
201
 
154
202
  .dark {
155
- --immich-ui-primary-50: oklch(0.982 0.007 268.63);
156
- --immich-ui-primary-100: oklch(0.966 0.015 260.75);
157
- --immich-ui-primary-200: oklch(0.932 0.03 262.55);
158
- --immich-ui-primary-300: oklch(0.906 0.041 260.95);
159
- --immich-ui-primary-400: oklch(0.871 0.057 259.89);
203
+ --immich-ui-primary-950: oklch(0.982 0.007 268.63);
204
+ --immich-ui-primary-900: oklch(0.966 0.015 260.75);
205
+ --immich-ui-primary-800: oklch(0.932 0.03 262.55);
206
+ --immich-ui-primary-700: oklch(0.906 0.041 260.95);
207
+ --immich-ui-primary-600: oklch(0.871 0.057 259.89);
160
208
  --immich-ui-primary-500: oklch(0.836 0.074 258.58);
161
- --immich-ui-primary-600: oklch(0.526 0.063 256.86);
162
- --immich-ui-primary-700: oklch(0.449 0.054 257.6);
163
- --immich-ui-primary-800: oklch(0.372 0.045 257.42);
164
- --immich-ui-primary-900: oklch(0.258 0.032 258.31);
165
- --immich-ui-primary-950: oklch(0.198 0.023 257.29);
166
-
167
- --immich-ui-success-50: oklch(0.979 0.027 157.32);
168
- --immich-ui-success-100: oklch(0.969 0.043 155.86);
169
- --immich-ui-success-200: oklch(0.937 0.09 156.81);
170
- --immich-ui-success-300: oklch(0.894 0.164 156.29);
171
- --immich-ui-success-400: oklch(0.866 0.186 156.06);
209
+ --immich-ui-primary-400: oklch(0.526 0.063 256.86);
210
+ --immich-ui-primary-300: oklch(0.449 0.054 257.6);
211
+ --immich-ui-primary-200: oklch(0.372 0.045 257.42);
212
+ --immich-ui-primary-100: oklch(0.258 0.032 258.31);
213
+ --immich-ui-primary-50: oklch(0.198 0.023 257.29);
214
+
215
+ --immich-ui-success-950: oklch(0.979 0.027 157.32);
216
+ --immich-ui-success-900: oklch(0.969 0.043 155.86);
217
+ --immich-ui-success-800: oklch(0.937 0.09 156.81);
218
+ --immich-ui-success-700: oklch(0.894 0.164 156.29);
219
+ --immich-ui-success-600: oklch(0.866 0.186 156.06);
172
220
  --immich-ui-success-500: oklch(0.842 0.181 156.04);
173
- --immich-ui-success-600: oklch(0.699 0.151 155.85);
174
- --immich-ui-success-700: oklch(0.555 0.119 156.26);
175
- --immich-ui-success-800: oklch(0.419 0.089 156.41);
176
- --immich-ui-success-900: oklch(0.279 0.06 155.24);
177
- --immich-ui-success-950: oklch(0.181 0.04 155.24);
178
-
179
- --immich-ui-danger-50: oklch(0.975 0.011 17.5);
180
- --immich-ui-danger-100: oklch(0.943 0.025 17.67);
181
- --immich-ui-danger-200: oklch(0.894 0.049 18.13);
182
- --immich-ui-danger-300: oklch(0.836 0.079 18.9);
183
- --immich-ui-danger-400: oklch(0.781 0.112 19.92);
221
+ --immich-ui-success-400: oklch(0.699 0.151 155.85);
222
+ --immich-ui-success-300: oklch(0.555 0.119 156.26);
223
+ --immich-ui-success-200: oklch(0.419 0.089 156.41);
224
+ --immich-ui-success-100: oklch(0.279 0.06 155.24);
225
+ --immich-ui-success-50: oklch(0.181 0.04 155.24);
226
+
227
+ --immich-ui-danger-950: oklch(0.975 0.011 17.5);
228
+ --immich-ui-danger-900: oklch(0.943 0.025 17.67);
229
+ --immich-ui-danger-800: oklch(0.894 0.049 18.13);
230
+ --immich-ui-danger-700: oklch(0.836 0.079 18.9);
231
+ --immich-ui-danger-600: oklch(0.781 0.112 19.92);
184
232
  --immich-ui-danger-500: oklch(0.727 0.149 21.41);
185
- --immich-ui-danger-600: oklch(0.622 0.232 27.33);
186
- --immich-ui-danger-700: oklch(0.505 0.188 27.33);
187
- --immich-ui-danger-800: oklch(0.387 0.144 27.27);
188
- --immich-ui-danger-900: oklch(0.262 0.097 27.31);
189
- --immich-ui-danger-950: oklch(0.207 0.077 27.3);
190
-
191
- --immich-ui-warning-50: oklch(0.993 0.005 67.8);
192
- --immich-ui-warning-100: oklch(0.975 0.017 67.63);
193
- --immich-ui-warning-200: oklch(0.958 0.029 67.47);
194
- --immich-ui-warning-300: oklch(0.934 0.048 69.18);
195
- --immich-ui-warning-400: oklch(0.907 0.07 70.22);
233
+ --immich-ui-danger-400: oklch(0.622 0.232 27.33);
234
+ --immich-ui-danger-300: oklch(0.505 0.188 27.33);
235
+ --immich-ui-danger-200: oklch(0.387 0.144 27.27);
236
+ --immich-ui-danger-100: oklch(0.262 0.097 27.31);
237
+ --immich-ui-danger-50: oklch(0.207 0.077 27.3);
238
+
239
+ --immich-ui-warning-950: oklch(0.993 0.005 67.8);
240
+ --immich-ui-warning-900: oklch(0.975 0.017 67.63);
241
+ --immich-ui-warning-800: oklch(0.958 0.029 67.47);
242
+ --immich-ui-warning-700: oklch(0.934 0.048 69.18);
243
+ --immich-ui-warning-600: oklch(0.907 0.07 70.22);
196
244
  --immich-ui-warning-500: oklch(0.888 0.089 72.24);
197
- --immich-ui-warning-600: oklch(0.738 0.153 79.79);
198
- --immich-ui-warning-700: oklch(0.589 0.122 79.52);
199
- --immich-ui-warning-800: oklch(0.442 0.092 79.1);
200
- --immich-ui-warning-900: oklch(0.286 0.059 78.61);
201
- --immich-ui-warning-950: oklch(0.197 0.039 78.61);
202
-
203
- --immich-ui-info-50: oklch(0.975 0.012 259.84);
204
- --immich-ui-info-100: oklch(0.956 0.021 261.78);
205
- --immich-ui-info-200: oklch(0.905 0.046 259.51);
206
- --immich-ui-info-300: oklch(0.861 0.068 259.36);
207
- --immich-ui-info-400: oklch(0.811 0.096 255.92);
245
+ --immich-ui-warning-400: oklch(0.738 0.153 79.79);
246
+ --immich-ui-warning-300: oklch(0.589 0.122 79.52);
247
+ --immich-ui-warning-200: oklch(0.442 0.092 79.1);
248
+ --immich-ui-warning-100: oklch(0.286 0.059 78.61);
249
+ --immich-ui-warning-50: oklch(0.197 0.039 78.61);
250
+
251
+ --immich-ui-info-950: oklch(0.975 0.012 259.84);
252
+ --immich-ui-info-900: oklch(0.956 0.021 261.78);
253
+ --immich-ui-info-800: oklch(0.905 0.046 259.51);
254
+ --immich-ui-info-700: oklch(0.861 0.068 259.36);
255
+ --immich-ui-info-600: oklch(0.811 0.096 255.92);
208
256
  --immich-ui-info-500: oklch(0.767 0.122 253.35);
209
- --immich-ui-info-600: oklch(0.638 0.166 246.52);
210
- --immich-ui-info-700: oklch(0.516 0.135 246.69);
211
- --immich-ui-info-800: oklch(0.386 0.1 246.5);
212
- --immich-ui-info-900: oklch(0.265 0.07 247.05);
213
- --immich-ui-info-950: oklch(0.186 0.049 247.05);
214
-
215
- --immich-ui-light: 0 0 0;
216
- --immich-ui-dark: rgb(221, 221, 221);
217
- --immich-ui-muted: 212 212 212;
218
- --immich-ui-gray: 33 33 33;
219
- --immich-ui-default-border: 33 33 33;
257
+ --immich-ui-info-400: oklch(0.638 0.166 246.52);
258
+ --immich-ui-info-300: oklch(0.516 0.135 246.69);
259
+ --immich-ui-info-200: oklch(0.386 0.1 246.5);
260
+ --immich-ui-info-100: oklch(0.265 0.07 247.05);
261
+ --immich-ui-info-50: oklch(0.186 0.049 247.05);
262
+
263
+ --immich-ui-light-50: var(--color-neutral-950);
264
+ --immich-ui-light-100: var(--color-neutral-900);
265
+ --immich-ui-light-200: var(--color-neutral-800);
266
+ --immich-ui-light-300: var(--color-neutral-700);
267
+ --immich-ui-light-400: var(--color-neutral-600);
268
+ --immich-ui-light-500: var(--color-neutral-500);
269
+ --immich-ui-light-600: var(--color-neutral-400);
270
+ --immich-ui-light-700: var(--color-neutral-300);
271
+ --immich-ui-light-800: var(--color-neutral-200);
272
+ --immich-ui-light-900: var(--color-neutral-100);
273
+ --immich-ui-light-950: var(--color-neutral-50);
274
+
275
+ --immich-ui-dark-50: var(--color-neutral-50);
276
+ --immich-ui-dark-100: var(--color-neutral-100);
277
+ --immich-ui-dark-200: var(--color-neutral-200);
278
+ --immich-ui-dark-300: var(--color-neutral-300);
279
+ --immich-ui-dark-400: var(--color-neutral-400);
280
+ --immich-ui-dark-500: var(--color-neutral-500);
281
+ --immich-ui-dark-600: var(--color-neutral-600);
282
+ --immich-ui-dark-700: var(--color-neutral-700);
283
+ --immich-ui-dark-800: var(--color-neutral-800);
284
+ --immich-ui-dark-900: var(--color-neutral-900);
285
+ --immich-ui-dark-950: var(--color-neutral-950);
286
+
287
+ --immich-ui-light: oklch(0% 0 0);
288
+ --immich-ui-dark: oklch(89% 0 271);
289
+ --immich-ui-muted: oklch(87% 0 271);
290
+ --immich-ui-gray: oklch(25% 0 271);
291
+ --immich-ui-default-border: var(--immich-ui-light-200);
220
292
  }
221
293
 
222
294
  *,
@@ -224,6 +296,6 @@
224
296
  ::before,
225
297
  ::backdrop,
226
298
  ::file-selector-button {
227
- border-color: rgb(var(--immich-ui-default-border));
299
+ border-color: var(--immich-ui-default-border);
228
300
  }
229
301
  }
package/dist/types.d.ts CHANGED
@@ -15,7 +15,6 @@ export type HeadingSize = Size | 'title';
15
15
  export type HeadingTag = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p';
16
16
  export type Shape = 'rectangle' | 'semi-round' | 'round';
17
17
  export type Variants = 'filled' | 'outline' | 'ghost';
18
- export type ToastVariant = 'filled' | 'outline';
19
18
  export type Gap = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
20
19
  export declare enum Theme {
21
20
  Light = "light",
@@ -68,6 +67,13 @@ export type CloseButtonProps = {
68
67
  class?: string;
69
68
  translations?: TranslationProps<'close'>;
70
69
  } & ButtonOrAnchor;
70
+ export type ContextMenuButtonProps = ButtonBase & {
71
+ icon?: IconLike;
72
+ position?: ContextMenuPosition;
73
+ 'aria-label': string;
74
+ items: MenuItems;
75
+ bottomItems?: Array<ActionItem | undefined>;
76
+ } & Omit<HTMLButtonAttributes, 'color' | 'size'>;
71
77
  export type IconButtonProps = ButtonBase & {
72
78
  icon: IconLike;
73
79
  flipped?: boolean;
@@ -189,7 +195,6 @@ export type ToastShow = {
189
195
  shape?: Shape;
190
196
  icon?: IconLike | false;
191
197
  size?: ContainerSize;
192
- variant?: ToastVariant;
193
198
  };
194
199
  export type ToastOptions = {
195
200
  id?: string;
@@ -205,23 +210,13 @@ export type ToastButton = {
205
210
  variant?: Variants;
206
211
  onClick: () => void;
207
212
  };
208
- export type MenuSelectHandler = (context: {
209
- event: Event;
210
- item: MenuItem;
211
- }) => void;
212
- export type MenuItem = {
213
- title: string;
214
- icon: IconLike;
215
- color?: Color;
216
- onSelect?: MenuSelectHandler;
217
- } & IfLike;
218
213
  export declare enum MenuItemType {
219
214
  Divider = "divider"
220
215
  }
221
- export type MenuItems = Array<MenuItem | MenuItemType | undefined>;
216
+ export type MenuItems = Array<ActionItem | MenuItemType | undefined>;
222
217
  export type MenuProps = {
223
218
  items: MenuItems;
224
- bottomItems?: (MenuItem | undefined)[];
219
+ bottomItems?: (ActionItem | undefined)[];
225
220
  size?: MenuSize;
226
221
  } & HTMLAttributes<HTMLDivElement>;
227
222
  export type ContextMenuPosition = 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
@@ -244,4 +239,14 @@ export type DatePickerProps = {
244
239
  export type IfLike = {
245
240
  $if?: () => boolean;
246
241
  };
242
+ export type ActionItemHandler<T = never> = (item: ActionItem<T>) => void | Promise<void>;
243
+ export type ActionItem<T = never> = Omit<{
244
+ title: string;
245
+ icon: IconLike;
246
+ color?: Color;
247
+ onAction: ActionItemHandler<T>;
248
+ data: T;
249
+ } & IfLike, [
250
+ T
251
+ ] extends [never] ? 'data' : ''>;
247
252
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immich/ui",
3
- "version": "0.46.0",
3
+ "version": "0.48.0",
4
4
  "license": "GNU Affero General Public License version 3",
5
5
  "repository": {
6
6
  "type": "git",
@@ -58,7 +58,7 @@
58
58
  "@immich/svelte-markdown-preprocess": "^0.1.0"
59
59
  },
60
60
  "volta": {
61
- "node": "24.11.0"
61
+ "node": "24.11.1"
62
62
  },
63
63
  "scripts": {
64
64
  "create": "node scripts/create.js",
@@ -1,38 +0,0 @@
1
- <script lang="ts">
2
- import type { Snippet } from 'svelte';
3
-
4
- interface Props {
5
- title?: string | undefined;
6
- description?: string | undefined;
7
- scrollbar?: boolean;
8
- buttons?: Snippet;
9
- children?: Snippet;
10
- }
11
-
12
- let { title = undefined, description = undefined, scrollbar = true, buttons, children }: Props = $props();
13
-
14
- let scrollbarClass = $derived(scrollbar ? 'immich-scrollbar p-2 pb-8' : 'scrollbar-hidden');
15
- let hasTitleClass = $derived(title ? 'top-16 h-[calc(100%-(--spacing(16)))]' : 'top-0 h-full');
16
- </script>
17
-
18
- <section class="relative">
19
- {#if title || buttons}
20
- <div
21
- class="dark:border-immich-neutral dark:text-immich-dark-fg absolute flex h-16 w-full place-items-center justify-between border-b p-4"
22
- >
23
- <div class="flex items-center gap-2">
24
- {#if title}
25
- <div class="font-medium" tabindex="-1">{title}</div>
26
- {/if}
27
- {#if description}
28
- <p class="text-sm text-gray-400 dark:text-gray-600">{description}</p>
29
- {/if}
30
- </div>
31
- {@render buttons?.()}
32
- </div>
33
- {/if}
34
-
35
- <div class="{scrollbarClass} scrollbar-stable absolute {hasTitleClass} w-full overflow-y-auto">
36
- {@render children?.()}
37
- </div>
38
- </section>
@@ -1,11 +0,0 @@
1
- import type { Snippet } from 'svelte';
2
- interface Props {
3
- title?: string | undefined;
4
- description?: string | undefined;
5
- scrollbar?: boolean;
6
- buttons?: Snippet;
7
- children?: Snippet;
8
- }
9
- declare const PageLayout: import("svelte").Component<Props, {}, "">;
10
- type PageLayout = ReturnType<typeof PageLayout>;
11
- export default PageLayout;