@immich/ui 0.58.4 → 0.60.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 (40) hide show
  1. package/dist/common/use-child.svelte.d.ts +1 -1
  2. package/dist/common/use-child.svelte.js +2 -0
  3. package/dist/components/ActionBar/ActionBar.svelte +22 -0
  4. package/dist/components/ActionBar/ActionBar.svelte.d.ts +4 -0
  5. package/dist/components/ActionButton/ActionButton.svelte +33 -0
  6. package/dist/components/ActionButton/ActionButton.svelte.d.ts +15 -0
  7. package/dist/components/AppShell/AppShell.svelte +10 -5
  8. package/dist/components/AppShell/AppShellBar.svelte +16 -0
  9. package/dist/components/AppShell/AppShellBar.svelte.d.ts +8 -0
  10. package/dist/components/AppShell/AppShellSidebar.svelte +18 -8
  11. package/dist/components/AppShell/AppShellSidebar.svelte.d.ts +1 -0
  12. package/dist/components/ContextMenu/ContextMenuButton.svelte +11 -9
  13. package/dist/components/ControlBar/ControlBar.svelte +92 -0
  14. package/dist/components/ControlBar/ControlBar.svelte.d.ts +4 -0
  15. package/dist/components/ControlBar/ControlBarContent.svelte +16 -0
  16. package/dist/components/ControlBar/ControlBarContent.svelte.d.ts +8 -0
  17. package/dist/components/ControlBar/ControlBarDescription.svelte +15 -0
  18. package/dist/components/ControlBar/ControlBarDescription.svelte.d.ts +8 -0
  19. package/dist/components/ControlBar/ControlBarHeader.svelte +16 -0
  20. package/dist/components/ControlBar/ControlBarHeader.svelte.d.ts +8 -0
  21. package/dist/components/ControlBar/ControlBarOverflow.svelte +16 -0
  22. package/dist/components/ControlBar/ControlBarOverflow.svelte.d.ts +8 -0
  23. package/dist/components/ControlBar/ControlBarTitle.svelte +23 -0
  24. package/dist/components/ControlBar/ControlBarTitle.svelte.d.ts +16 -0
  25. package/dist/components/Navbar/NavbarGroup.svelte +24 -3
  26. package/dist/components/Navbar/NavbarGroup.svelte.d.ts +7 -0
  27. package/dist/components/Navbar/NavbarItem.svelte +68 -33
  28. package/dist/components/Navbar/NavbarItem.svelte.d.ts +3 -11
  29. package/dist/components/ProgressBar/ProgressBar.svelte +31 -9
  30. package/dist/components/ProgressBar/ProgressBar.svelte.d.ts +1 -0
  31. package/dist/constants.d.ts +6 -0
  32. package/dist/constants.js +6 -0
  33. package/dist/index.d.ts +9 -0
  34. package/dist/index.js +9 -0
  35. package/dist/internal/Child.svelte +6 -5
  36. package/dist/services/translation.svelte.d.ts +2 -0
  37. package/dist/services/translation.svelte.js +2 -0
  38. package/dist/theme/default.css +5 -0
  39. package/dist/types.d.ts +33 -0
  40. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  import { ChildKey } from '../constants.js';
2
- import type { ChildData } from '../types.js';
2
+ import { type ChildData } from '../types.js';
3
3
  export declare const withChildrenSnippets: (key: ChildKey) => {
4
4
  getChildren: <T>(key: ChildKey) => ChildData<T> | undefined;
5
5
  };
@@ -1,4 +1,5 @@
1
1
  import { ChildKey } from '../constants.js';
2
+ import {} from '../types.js';
2
3
  import { withPrefix } from '../utilities/internal.js';
3
4
  import { setContext } from 'svelte';
4
5
  import { SvelteMap } from 'svelte/reactivity';
@@ -6,6 +7,7 @@ export const withChildrenSnippets = (key) => {
6
7
  const map = new SvelteMap();
7
8
  setContext(withPrefix(key), {
8
9
  register: (child, data) => map.set(child, data),
10
+ unregister: (child) => map.delete(child),
9
11
  });
10
12
  return {
11
13
  getChildren: (key) => map.get(key)?.(),
@@ -0,0 +1,22 @@
1
+ <script lang="ts">
2
+ import ActionButton from '../ActionButton/ActionButton.svelte';
3
+ import ContextMenuButton from '../ContextMenu/ContextMenuButton.svelte';
4
+ import ControlBar from '../ControlBar/ControlBar.svelte';
5
+ import ControlBarOverflow from '../ControlBar/ControlBarOverflow.svelte';
6
+ import type { ActionBarProps } from '../../types.js';
7
+ import { isEnabled } from '../../utilities/common.js';
8
+
9
+ const { actions, overflowActions = [], children, ...restProps }: ActionBarProps = $props();
10
+
11
+ const items = $derived(overflowActions.filter((action) => isEnabled(action)));
12
+ </script>
13
+
14
+ <ControlBar {...restProps}>
15
+ {@render children?.()}
16
+ <ControlBarOverflow>
17
+ {#each actions as action, i (i)}
18
+ <ActionButton {action} />
19
+ {/each}
20
+ <ContextMenuButton {items} />
21
+ </ControlBarOverflow>
22
+ </ControlBar>
@@ -0,0 +1,4 @@
1
+ import type { ActionBarProps } from '../../types.js';
2
+ declare const ActionBar: import("svelte").Component<ActionBarProps, {}, "">;
3
+ type ActionBar = ReturnType<typeof ActionBar>;
4
+ export default ActionBar;
@@ -0,0 +1,33 @@
1
+ <script lang="ts">
2
+ import Button from '../Button/Button.svelte';
3
+ import IconButton from '../IconButton/IconButton.svelte';
4
+ import type { ActionItem, Color, Size, Variants } from '../../types.js';
5
+ import { isEnabled } from '../../utilities/common.js';
6
+ import { mdiPlus } from '@mdi/js';
7
+
8
+ type Props = {
9
+ action: ActionItem & { data?: { title?: string } };
10
+ color?: Color;
11
+ size?: Size;
12
+ variant?: Variants;
13
+ type?: 'icon' | 'button';
14
+ };
15
+
16
+ const { action, type = 'icon', size, color: colorOverride, variant: variantOverride }: Props = $props();
17
+ const { title, icon, onAction } = $derived(action);
18
+ const common = $derived({
19
+ variant: variantOverride ?? 'ghost',
20
+ color: colorOverride ?? action.color ?? 'secondary',
21
+ onclick: () => onAction(action),
22
+ });
23
+ </script>
24
+
25
+ {#if isEnabled(action)}
26
+ {#if type === 'icon'}
27
+ <IconButton {...common} {size} shape="round" icon={icon ?? mdiPlus} aria-label={title} />
28
+ {:else if type === 'button'}
29
+ <Button {...common} size={size ?? 'small'} leadingIcon={icon} title={action.data?.title}>
30
+ {title}
31
+ </Button>
32
+ {/if}
33
+ {/if}
@@ -0,0 +1,15 @@
1
+ import type { ActionItem, Color, Size, Variants } from '../../types.js';
2
+ type Props = {
3
+ action: ActionItem & {
4
+ data?: {
5
+ title?: string;
6
+ };
7
+ };
8
+ color?: Color;
9
+ size?: Size;
10
+ variant?: Variants;
11
+ type?: 'icon' | 'button';
12
+ };
13
+ declare const ActionButton: import("svelte").Component<Props, {}, "">;
14
+ type ActionButton = ReturnType<typeof ActionButton>;
15
+ export default ActionButton;
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { withChildrenSnippets } from '../../common/use-child.svelte.js';
3
3
  import Scrollable from '../Scrollable/Scrollable.svelte';
4
- import { ChildKey } from '../../constants.js';
4
+ import { ChildKey, zIndex } from '../../constants.js';
5
5
  import { cleanClass } from '../../utilities/internal.js';
6
6
  import type { Snippet } from 'svelte';
7
7
 
@@ -13,19 +13,24 @@
13
13
  const { class: className, children }: Props = $props();
14
14
 
15
15
  const { getChildren: getChildSnippet } = withChildrenSnippets(ChildKey.AppShell);
16
+ const bar = $derived(getChildSnippet(ChildKey.AppShellBar));
16
17
  const header = $derived(getChildSnippet(ChildKey.AppShellHeader));
17
18
  const sidebar = $derived(getChildSnippet(ChildKey.AppShellSidebar));
18
19
  </script>
19
20
 
20
21
  <div class={cleanClass('flex h-dvh flex-col overflow-hidden', className)}>
21
- {#if header}
22
- <header class="border-b">
23
- {@render header?.snippet()}
22
+ {#if bar}
23
+ <div class={cleanClass('h-control-bar-container px-2 pt-2', zIndex.AppShellBar, bar.class)}>
24
+ {@render bar.snippet()}
25
+ </div>
26
+ {:else if header}
27
+ <header class={cleanClass('h-control-bar-container flex items-center gap-2 border-b', header.class)}>
28
+ {@render header.snippet()}
24
29
  </header>
25
30
  {/if}
26
31
  <div class="relative flex w-full grow overflow-y-auto">
27
32
  {#if sidebar}
28
- {@render sidebar?.snippet()}
33
+ {@render sidebar.snippet()}
29
34
  {/if}
30
35
  <Scrollable class="grow" resetOnNavigate>
31
36
  {@render children?.()}
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import { ChildKey } from '../../constants.js';
3
+ import Child from '../../internal/Child.svelte';
4
+ import type { Snippet } from 'svelte';
5
+
6
+ type Props = {
7
+ children: Snippet;
8
+ class?: string;
9
+ };
10
+
11
+ let { children, class: className }: Props = $props();
12
+ </script>
13
+
14
+ <Child for={ChildKey.AppShell} as={ChildKey.AppShellBar} class={className}>
15
+ {@render children?.()}
16
+ </Child>
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Props = {
3
+ children: Snippet;
4
+ class?: string;
5
+ };
6
+ declare const AppShellBar: import("svelte").Component<Props, {}, "">;
7
+ type AppShellBar = ReturnType<typeof AppShellBar>;
8
+ export default AppShellBar;
@@ -4,24 +4,34 @@
4
4
  import Child from '../../internal/Child.svelte';
5
5
  import { cleanClass } from '../../utilities/internal.js';
6
6
  import { type Snippet } from 'svelte';
7
+ import { tv } from 'tailwind-variants';
7
8
 
8
9
  type Props = {
9
10
  class?: string;
10
11
  children: Snippet;
11
12
  open?: boolean;
13
+ border?: boolean;
12
14
  };
13
15
 
14
- let { class: className, children, open = $bindable(true) }: Props = $props();
16
+ let { class: className, border = true, children, open = $bindable(true) }: Props = $props();
17
+
18
+ const styles = tv({
19
+ base: 'bg-light text-dark absolute shrink-0 transition-all duration-200 md:relative',
20
+ variants: {
21
+ border: {
22
+ true: 'border-e shadow-lg',
23
+ false: '',
24
+ },
25
+ open: {
26
+ true: `${zIndex.AppShellSidebar} w-[min(100vw,16rem)]`,
27
+ false: 'w-0 border-e-0',
28
+ },
29
+ },
30
+ });
15
31
  </script>
16
32
 
17
33
  <Child for={ChildKey.AppShell} as={ChildKey.AppShellSidebar}>
18
- <Scrollable
19
- class={cleanClass(
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-0 border-e-0',
22
- className,
23
- )}
24
- >
34
+ <Scrollable class={cleanClass(styles({ border, open }), className)}>
25
35
  {@render children?.()}
26
36
  </Scrollable>
27
37
  </Child>
@@ -3,6 +3,7 @@ type Props = {
3
3
  class?: string;
4
4
  children: Snippet;
5
5
  open?: boolean;
6
+ border?: boolean;
6
7
  };
7
8
  declare const AppShellSidebar: import("svelte").Component<Props, {}, "open">;
8
9
  type AppShellSidebar = ReturnType<typeof AppShellSidebar>;
@@ -23,12 +23,14 @@
23
23
  };
24
24
  </script>
25
25
 
26
- <IconButton
27
- {icon}
28
- {color}
29
- {shape}
30
- {variant}
31
- aria-label={ariaLabel ?? t('open_menu', translations)}
32
- {...rest}
33
- {onclick}
34
- />
26
+ {#if items.length > 0}
27
+ <IconButton
28
+ {icon}
29
+ {color}
30
+ {shape}
31
+ {variant}
32
+ aria-label={ariaLabel ?? t('open_menu', translations)}
33
+ {...rest}
34
+ {onclick}
35
+ />
36
+ {/if}
@@ -0,0 +1,92 @@
1
+ <script lang="ts">
2
+ import { shortcuts } from '../../actions/shortcut.js';
3
+ import { withChildrenSnippets } from '../../common/use-child.svelte.js';
4
+ import IconButton from '../IconButton/IconButton.svelte';
5
+ import { ChildKey } from '../../constants.js';
6
+ import { t } from '../../services/translation.svelte.js';
7
+ import type { ControlBarProps } from '../../types.js';
8
+ import { cleanClass } from '../../utilities/internal.js';
9
+ import { mdiClose } from '@mdi/js';
10
+ import { fly } from 'svelte/transition';
11
+ import { tv } from 'tailwind-variants';
12
+
13
+ let {
14
+ ref = $bindable(null),
15
+ closeIcon,
16
+ class: className,
17
+ shape = 'semi-round',
18
+ variant = 'ghost',
19
+ closeOnEsc = true,
20
+ static: isStatic = false,
21
+ translations,
22
+ onClose,
23
+ children,
24
+ ...restProps
25
+ }: ControlBarProps = $props();
26
+
27
+ const styles = tv({
28
+ base: `h-control-bar flex w-full items-center gap-2 px-2`,
29
+ variants: {
30
+ variant: {
31
+ filled: 'dark:bg-subtle bg-white shadow-lg',
32
+ outline: 'dark:border-light-200 border-light-200 border shadow-md',
33
+ ghost: '',
34
+ },
35
+ shape: {
36
+ 'semi-round': 'rounded-lg',
37
+ rectangle: 'rounded-none',
38
+ },
39
+ },
40
+ });
41
+
42
+ const onEscape = () => {
43
+ if (closeOnEsc) {
44
+ onClose?.();
45
+ }
46
+ };
47
+
48
+ const { getChildren: getChildSnippet } = withChildrenSnippets(ChildKey.ControlBar);
49
+ const headerChild = $derived(getChildSnippet(ChildKey.ControlBarHeader));
50
+ const contentChild = $derived(getChildSnippet(ChildKey.ControlBarContent));
51
+ const overflowChild = $derived(getChildSnippet(ChildKey.ControlBarOverflow));
52
+ </script>
53
+
54
+ <svelte:window use:shortcuts={[{ shortcut: { key: 'Escape' }, onShortcut: onEscape }]} />
55
+
56
+ <nav
57
+ bind:this={ref}
58
+ in:fly={{ y: 10, duration: isStatic ? 0 : 200 }}
59
+ class={cleanClass(styles({ shape, variant }), className)}
60
+ {...restProps}
61
+ >
62
+ <div class={cleanClass('flex shrink-0 items-center gap-2')}>
63
+ {#if typeof closeIcon === 'function'}
64
+ {@render closeIcon?.()}
65
+ {:else if onClose}
66
+ <IconButton
67
+ icon={closeIcon ?? mdiClose}
68
+ shape="round"
69
+ variant="ghost"
70
+ color="secondary"
71
+ aria-label={t('close', translations)}
72
+ onclick={() => onClose()}
73
+ />
74
+ {/if}
75
+
76
+ <div class={cleanClass('flex shrink-0 flex-col')}>
77
+ {@render headerChild?.snippet()}
78
+ </div>
79
+ </div>
80
+
81
+ <div class={cleanClass('flex grow items-center gap-2', contentChild?.class)}>
82
+ {@render contentChild?.snippet()}
83
+ </div>
84
+
85
+ {#if overflowChild}
86
+ <div class={cleanClass('flex shrink-0 items-center gap-2', overflowChild.class)}>
87
+ {@render overflowChild.snippet()}
88
+ </div>
89
+ {/if}
90
+
91
+ {@render children?.()}
92
+ </nav>
@@ -0,0 +1,4 @@
1
+ import type { ControlBarProps } from '../../types.js';
2
+ declare const ControlBar: import("svelte").Component<ControlBarProps, {}, "ref">;
3
+ type ControlBar = ReturnType<typeof ControlBar>;
4
+ export default ControlBar;
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import { ChildKey } from '../../constants.js';
3
+ import Child from '../../internal/Child.svelte';
4
+ import type { Snippet } from 'svelte';
5
+
6
+ type Props = {
7
+ class?: string;
8
+ children: Snippet;
9
+ };
10
+
11
+ let { class: className, children }: Props = $props();
12
+ </script>
13
+
14
+ <Child for={ChildKey.ControlBar} as={ChildKey.ControlBarContent} class={className}>
15
+ {@render children?.()}
16
+ </Child>
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Props = {
3
+ class?: string;
4
+ children: Snippet;
5
+ };
6
+ declare const ControlBarContent: import("svelte").Component<Props, {}, "">;
7
+ type ControlBarContent = ReturnType<typeof ControlBarContent>;
8
+ export default ControlBarContent;
@@ -0,0 +1,15 @@
1
+ <script lang="ts">
2
+ import Text from '../Text/Text.svelte';
3
+ import type { Snippet } from 'svelte';
4
+
5
+ type Props = {
6
+ class?: string;
7
+ children: Snippet;
8
+ };
9
+
10
+ let { children, class: className }: Props = $props();
11
+ </script>
12
+
13
+ <Text size="small" color="muted" class={className}>
14
+ {@render children?.()}
15
+ </Text>
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Props = {
3
+ class?: string;
4
+ children: Snippet;
5
+ };
6
+ declare const ControlBarDescription: import("svelte").Component<Props, {}, "">;
7
+ type ControlBarDescription = ReturnType<typeof ControlBarDescription>;
8
+ export default ControlBarDescription;
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import { ChildKey } from '../../constants.js';
3
+ import Child from '../../internal/Child.svelte';
4
+ import type { Snippet } from 'svelte';
5
+
6
+ type Props = {
7
+ class?: string;
8
+ children: Snippet;
9
+ };
10
+
11
+ let { class: className, children }: Props = $props();
12
+ </script>
13
+
14
+ <Child for={ChildKey.ControlBar} as={ChildKey.ControlBarHeader} class={className}>
15
+ {@render children?.()}
16
+ </Child>
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Props = {
3
+ class?: string;
4
+ children: Snippet;
5
+ };
6
+ declare const ControlBarHeader: import("svelte").Component<Props, {}, "">;
7
+ type ControlBarHeader = ReturnType<typeof ControlBarHeader>;
8
+ export default ControlBarHeader;
@@ -0,0 +1,16 @@
1
+ <script lang="ts">
2
+ import { ChildKey } from '../../constants.js';
3
+ import Child from '../../internal/Child.svelte';
4
+ import type { Snippet } from 'svelte';
5
+
6
+ type Props = {
7
+ class?: string;
8
+ children: Snippet;
9
+ };
10
+
11
+ let { class: className, children }: Props = $props();
12
+ </script>
13
+
14
+ <Child for={ChildKey.ControlBar} as={ChildKey.ControlBarOverflow} class={className}>
15
+ {@render children?.()}
16
+ </Child>
@@ -0,0 +1,8 @@
1
+ import type { Snippet } from 'svelte';
2
+ type Props = {
3
+ class?: string;
4
+ children: Snippet;
5
+ };
6
+ declare const ControlBarOverflow: import("svelte").Component<Props, {}, "">;
7
+ type ControlBarOverflow = ReturnType<typeof ControlBarOverflow>;
8
+ export default ControlBarOverflow;
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import Heading from '../Heading/Heading.svelte';
3
+ import type { Color, FontWeight, HeadingSize, HeadingTag } from '../../types.js';
4
+ import type { Snippet } from 'svelte';
5
+
6
+ type Props = {
7
+ /**
8
+ * The HTML element type.
9
+ */
10
+ tag?: HeadingTag;
11
+ class?: string;
12
+ size?: HeadingSize;
13
+ color?: Color;
14
+ fontWeight?: FontWeight;
15
+ children: Snippet;
16
+ };
17
+
18
+ const { size = 'small', fontWeight = 'medium', color = 'primary', tag, class: className, children }: Props = $props();
19
+ </script>
20
+
21
+ <Heading {tag} {size} {color} {fontWeight} class={className}>
22
+ {@render children?.()}
23
+ </Heading>
@@ -0,0 +1,16 @@
1
+ import type { Color, FontWeight, HeadingSize, HeadingTag } from '../../types.js';
2
+ import type { Snippet } from 'svelte';
3
+ type Props = {
4
+ /**
5
+ * The HTML element type.
6
+ */
7
+ tag?: HeadingTag;
8
+ class?: string;
9
+ size?: HeadingSize;
10
+ color?: Color;
11
+ fontWeight?: FontWeight;
12
+ children: Snippet;
13
+ };
14
+ declare const ControlBarTitle: import("svelte").Component<Props, {}, "">;
15
+ type ControlBarTitle = ReturnType<typeof ControlBarTitle>;
16
+ export default ControlBarTitle;
@@ -1,11 +1,32 @@
1
1
  <script lang="ts">
2
+ import Text from '../Text/Text.svelte';
3
+ import type { FontWeight, NavbarVariant, Size, TextColor } from '../../types.js';
4
+ import { cleanClass } from '../../utilities/internal.js';
5
+ import { tv } from 'tailwind-variants';
6
+
2
7
  type Props = {
3
8
  title: string;
9
+ size?: Size;
10
+ color?: TextColor;
11
+ fontWeight?: FontWeight;
12
+ variant?: NavbarVariant;
13
+ inline?: boolean;
14
+ class?: string;
4
15
  };
5
16
 
6
- let { title }: Props = $props();
17
+ let { title, size = 'small', variant, fontWeight, color, class: className }: Props = $props();
18
+
19
+ const styles = tv({
20
+ base: 'ps-6 transition-all duration-200',
21
+ variants: {
22
+ variant: {
23
+ compact: 'py-4',
24
+ default: 'py-6',
25
+ },
26
+ },
27
+ });
7
28
  </script>
8
29
 
9
- <div class="text-sm transition-all duration-200 md:text-sm">
10
- <p class="py-2 ps-4">{title}</p>
30
+ <div class={cleanClass(styles({ variant: variant ?? 'default' }), className)}>
31
+ <Text {color} {size} {fontWeight}>{title}</Text>
11
32
  </div>
@@ -1,5 +1,12 @@
1
+ import type { FontWeight, NavbarVariant, Size, TextColor } from '../../types.js';
1
2
  type Props = {
2
3
  title: string;
4
+ size?: Size;
5
+ color?: TextColor;
6
+ fontWeight?: FontWeight;
7
+ variant?: NavbarVariant;
8
+ inline?: boolean;
9
+ class?: string;
3
10
  };
4
11
  declare const NavbarGroup: import("svelte").Component<Props, {}, "">;
5
12
  type NavbarGroup = ReturnType<typeof NavbarGroup>;
@@ -2,59 +2,94 @@
2
2
  import { page } from '$app/state';
3
3
  import Icon from '../Icon/Icon.svelte';
4
4
  import Link from '../Link/Link.svelte';
5
- import type { IconProps } from '../../types.js';
5
+ import NavbarItem from './NavbarItem.svelte';
6
+ import { t } from '../../services/translation.svelte.js';
7
+ import type { NavbarProps } from '../../types.js';
8
+ import { cleanClass } from '../../utilities/internal.js';
9
+ import { mdiChevronDown, mdiChevronRight } from '@mdi/js';
6
10
  import { tv } from 'tailwind-variants';
7
11
 
8
- type Props = {
9
- title: string;
10
- href: string;
11
- active?: boolean;
12
- variant?: 'compact';
13
- isActive?: () => boolean;
14
- icon?: string | IconProps;
15
- activeIcon?: string | IconProps;
16
- };
17
-
18
12
  const startsWithHref = () => page.url.pathname.startsWith(href);
19
13
 
20
- let { href, isActive: isActiveOverride, title, variant, active: activeOverride, icon, activeIcon }: Props = $props();
14
+ let {
15
+ href,
16
+ isActive: isActiveOverride,
17
+ title,
18
+ variant,
19
+ active: activeOverride,
20
+ icon,
21
+ activeIcon,
22
+ expanded = $bindable(false),
23
+ items,
24
+ class: className,
25
+ }: NavbarProps = $props();
21
26
 
22
- const isActive = isActiveOverride ?? startsWithHref;
27
+ const isActive = $derived(isActiveOverride ?? startsWithHref);
23
28
  let active = $derived(activeOverride ?? isActive());
24
-
25
29
  const iconProps = $derived(typeof icon === 'string' ? { icon } : icon);
26
30
  const activeIconProps = $derived(typeof activeIcon === 'string' ? { icon: activeIcon } : activeIcon);
27
31
 
28
32
  const styles = tv({
29
- base: 'hover:bg-subtle hover:text-primary flex w-full place-items-center gap-4 rounded-e-full px-5 transition-[padding] delay-100 duration-100 group-hover:sm:px-5',
33
+ base: 'hover:bg-subtle hover:text-primary flex w-full place-items-center gap-4 rounded-e-full ps-5 transition-[padding] delay-100 duration-100',
30
34
  variants: {
31
35
  active: {
32
36
  true: 'bg-primary/10 text-primary',
33
37
  false: '',
34
38
  },
35
39
  variant: {
36
- default: 'py-3 ps-5',
37
- compact: 'py-2 ps-3',
40
+ default: 'py-3',
41
+ compact: 'py-2',
38
42
  },
39
43
  },
40
44
  });
41
45
  </script>
42
46
 
43
- <Link
44
- {href}
45
- aria-current={active ? 'page' : undefined}
46
- underline={false}
47
- class={styles({ active, variant: variant ?? 'default' })}
48
- >
49
- <div class="flex w-full place-items-center gap-4 truncate overflow-hidden">
50
- {#if iconProps}
51
- <Icon
52
- size="1.375em"
53
- class="shrink-0"
54
- aria-hidden={true}
55
- {...active && activeIconProps ? activeIconProps : iconProps}
56
- />
47
+ <div>
48
+ <div class="relative flex items-center">
49
+ {#if items}
50
+ <button
51
+ type="button"
52
+ aria-label={expanded ? t('collapse') : t('expand')}
53
+ class="hover:bg-subtle hover:text-primary absolute me-2 hidden h-full rounded-lg px-0.5 md:block"
54
+ onclick={() => (expanded = !expanded)}
55
+ >
56
+ <Icon
57
+ icon={expanded ? mdiChevronDown : mdiChevronRight}
58
+ size="1em"
59
+ class="shrink-0 delay-100 duration-100 "
60
+ aria-hidden
61
+ />
62
+ </button>
57
63
  {/if}
58
- <span class="text-sm font-medium">{title}</span>
64
+ <Link
65
+ {href}
66
+ aria-current={active ? 'page' : undefined}
67
+ underline={false}
68
+ class={cleanClass(styles({ active, variant: variant ?? 'default' }), className)}
69
+ >
70
+ <div class="relative flex w-full place-items-center {variant === 'compact' ? 'gap-2' : 'gap-4'}">
71
+ {#if iconProps}
72
+ <Icon
73
+ size="1.375em"
74
+ class="shrink-0"
75
+ aria-hidden={true}
76
+ {...active && activeIconProps ? activeIconProps : iconProps}
77
+ />
78
+ {/if}
79
+ <span class="truncate text-sm font-medium">{title}</span>
80
+ </div>
81
+ </Link>
59
82
  </div>
60
- </Link>
83
+
84
+ {#if expanded}
85
+ <div>
86
+ {#if Array.isArray(items)}
87
+ {#each items as { class: className, ...item }, i (i)}
88
+ <NavbarItem {variant} {...item} class={cleanClass('ps-8', className)} />
89
+ {/each}
90
+ {:else if items}
91
+ {@render items()}
92
+ {/if}
93
+ </div>
94
+ {/if}
95
+ </div>
@@ -1,13 +1,5 @@
1
- import type { IconProps } from '../../types.js';
2
- type Props = {
3
- title: string;
4
- href: string;
5
- active?: boolean;
6
- variant?: 'compact';
7
- isActive?: () => boolean;
8
- icon?: string | IconProps;
9
- activeIcon?: string | IconProps;
10
- };
11
- declare const NavbarItem: import("svelte").Component<Props, {}, "">;
1
+ import NavbarItem from './NavbarItem.svelte';
2
+ import type { NavbarProps } from '../../types.js';
3
+ declare const NavbarItem: import("svelte").Component<NavbarProps, {}, "expanded">;
12
4
  type NavbarItem = ReturnType<typeof NavbarItem>;
13
5
  export default NavbarItem;
@@ -10,22 +10,31 @@
10
10
  size?: Size;
11
11
  shape?: Shape;
12
12
  color?: Color;
13
+ border?: boolean;
13
14
  class?: string;
14
15
  children?: Snippet;
15
16
  };
16
17
 
17
- let { progress, shape = 'round', size = 'medium', color = 'primary', class: className, children }: Props = $props();
18
+ let {
19
+ progress,
20
+ shape = 'round',
21
+ size = 'medium',
22
+ color = 'primary',
23
+ border = false,
24
+ class: className,
25
+ children,
26
+ }: Props = $props();
18
27
 
19
28
  const containerStyles = tv({
20
- base: 'bg-light-100 dark:bg-light-200 dark:border-light-300 relative w-full overflow-hidden border',
29
+ base: 'bg-light-100 dark:bg-light-200 relative w-full overflow-hidden',
21
30
  variants: {
22
31
  shape: styleVariants.shape,
23
32
  size: {
24
- tiny: 'h-3',
25
- small: 'h-4',
26
- medium: 'h-5',
27
- large: 'h-6',
28
- giant: 'h-12',
33
+ tiny: 'h-2',
34
+ small: 'h-3',
35
+ medium: 'h-4',
36
+ large: 'h-5',
37
+ giant: 'h-7',
29
38
  },
30
39
  roundedSize: {
31
40
  tiny: 'rounded-sm',
@@ -34,6 +43,9 @@
34
43
  large: 'rounded-lg',
35
44
  giant: 'rounded-xl',
36
45
  },
46
+ border: {
47
+ true: 'dark:border-light-300 border',
48
+ },
37
49
  },
38
50
  });
39
51
 
@@ -42,18 +54,28 @@
42
54
  variants: {
43
55
  color: styleVariants.filledColor,
44
56
  shape: styleVariants.shape,
57
+ size: {
58
+ tiny: 'min-w-2',
59
+ small: 'min-w-3',
60
+ medium: 'min-w-4',
61
+ large: 'min-w-5',
62
+ giant: 'min-w-7',
63
+ },
45
64
  },
46
65
  });
47
66
  </script>
48
67
 
49
68
  <div
50
69
  class={cleanClass(
51
- containerStyles({ size, shape, roundedSize: shape === 'semi-round' ? size : undefined }),
70
+ containerStyles({ size, shape, roundedSize: shape === 'semi-round' ? size : undefined, border }),
52
71
  className,
53
72
  )}
54
73
  >
55
74
  <div class="absolute flex h-full w-full items-center justify-center">
56
75
  {@render children?.()}
57
76
  </div>
58
- <div class={cleanClass(barStyles({ color, shape }))} style="width: {progress * 100}%"></div>
77
+ <div
78
+ class={cleanClass(barStyles({ size: progress > 0 ? size : undefined, color, shape }))}
79
+ style="width: {progress * 100}%"
80
+ ></div>
59
81
  </div>
@@ -5,6 +5,7 @@ type Props = {
5
5
  size?: Size;
6
6
  shape?: Shape;
7
7
  color?: Color;
8
+ border?: boolean;
8
9
  class?: string;
9
10
  children?: Snippet;
10
11
  };
@@ -3,11 +3,16 @@ export declare enum ChildKey {
3
3
  HelperText = "helped-text",
4
4
  AppShell = "app-shell",
5
5
  AppShellHeader = "app-shell-header",
6
+ AppShellBar = "app-shell-bar",
6
7
  AppShellSidebar = "app-shell-sidebar",
7
8
  Card = "card",
8
9
  CardHeader = "card-header",
9
10
  CardBody = "card-body",
10
11
  CardFooter = "card-footer",
12
+ ControlBar = "control-bar",
13
+ ControlBarHeader = "control-bar-header",
14
+ ControlBarContent = "control-bar-content",
15
+ ControlBarOverflow = "control-bar-overflow",
11
16
  Modal = "modal",
12
17
  ModalHeader = "modal-header",
13
18
  ModalBody = "modal-body",
@@ -20,6 +25,7 @@ export declare enum ChildKey {
20
25
  export declare const zIndex: {
21
26
  CarouselImage: string;
22
27
  AppShellSidebar: string;
28
+ AppShellBar: string;
23
29
  ModalBackdrop: string;
24
30
  ModalContent: string;
25
31
  SelectDropdown: string;
package/dist/constants.js CHANGED
@@ -4,11 +4,16 @@ export var ChildKey;
4
4
  ChildKey["HelperText"] = "helped-text";
5
5
  ChildKey["AppShell"] = "app-shell";
6
6
  ChildKey["AppShellHeader"] = "app-shell-header";
7
+ ChildKey["AppShellBar"] = "app-shell-bar";
7
8
  ChildKey["AppShellSidebar"] = "app-shell-sidebar";
8
9
  ChildKey["Card"] = "card";
9
10
  ChildKey["CardHeader"] = "card-header";
10
11
  ChildKey["CardBody"] = "card-body";
11
12
  ChildKey["CardFooter"] = "card-footer";
13
+ ChildKey["ControlBar"] = "control-bar";
14
+ ChildKey["ControlBarHeader"] = "control-bar-header";
15
+ ChildKey["ControlBarContent"] = "control-bar-content";
16
+ ChildKey["ControlBarOverflow"] = "control-bar-overflow";
12
17
  ChildKey["Modal"] = "modal";
13
18
  ChildKey["ModalHeader"] = "modal-header";
14
19
  ChildKey["ModalBody"] = "modal-body";
@@ -21,6 +26,7 @@ export var ChildKey;
21
26
  export const zIndex = {
22
27
  CarouselImage: 'z-1',
23
28
  AppShellSidebar: 'z-30',
29
+ AppShellBar: 'z-35',
24
30
  ModalBackdrop: 'z-40',
25
31
  ModalContent: 'z-50',
26
32
  SelectDropdown: 'z-55',
package/dist/index.d.ts CHANGED
@@ -10,9 +10,12 @@ export { default as immichLogoJson } from './assets/immich-logo.json';
10
10
  export { default as immichLogo } from './assets/immich-logo.svg';
11
11
  export { default as obtainiumBadge } from './assets/obtainium-badge.png';
12
12
  export { default as playStoreBadge } from './assets/playstore-badge.png';
13
+ export { default as ActionBar } from './components/ActionBar/ActionBar.svelte';
14
+ export { default as ActionButton } from './components/ActionButton/ActionButton.svelte';
13
15
  export { default as Alert } from './components/Alert/Alert.svelte';
14
16
  export { default as AnnouncementBanner } from './components/AnnouncementBanner/AnnouncementBanner.svelte';
15
17
  export { default as AppShell } from './components/AppShell/AppShell.svelte';
18
+ export { default as AppShellBar } from './components/AppShell/AppShellBar.svelte';
16
19
  export { default as AppShellHeader } from './components/AppShell/AppShellHeader.svelte';
17
20
  export { default as AppShellSidebar } from './components/AppShell/AppShellSidebar.svelte';
18
21
  export { default as Avatar } from './components/Avatar/Avatar.svelte';
@@ -35,6 +38,12 @@ export { default as CommandPaletteProvider } from './components/CommandPalette/C
35
38
  export { default as ConfirmModal } from './components/ConfirmModal/ConfirmModal.svelte';
36
39
  export { default as Container } from './components/Container/Container.svelte';
37
40
  export { default as ContextMenuButton } from './components/ContextMenu/ContextMenuButton.svelte';
41
+ export { default as ControlBar } from './components/ControlBar/ControlBar.svelte';
42
+ export { default as ControlBarContent } from './components/ControlBar/ControlBarContent.svelte';
43
+ export { default as ControlBarDescription } from './components/ControlBar/ControlBarDescription.svelte';
44
+ export { default as ControlBarHeader } from './components/ControlBar/ControlBarHeader.svelte';
45
+ export { default as ControlBarOverflow } from './components/ControlBar/ControlBarOverflow.svelte';
46
+ export { default as ControlBarTitle } from './components/ControlBar/ControlBarTitle.svelte';
38
47
  export { default as DatePicker } from './components/DatePicker/DatePicker.svelte';
39
48
  export { default as Field } from './components/Field/Field.svelte';
40
49
  export { default as FormatBytes } from './components/FormatBytes/FormatBytes.svelte';
package/dist/index.js CHANGED
@@ -12,9 +12,12 @@ export { default as immichLogo } from './assets/immich-logo.svg';
12
12
  export { default as obtainiumBadge } from './assets/obtainium-badge.png';
13
13
  export { default as playStoreBadge } from './assets/playstore-badge.png';
14
14
  // components
15
+ export { default as ActionBar } from './components/ActionBar/ActionBar.svelte';
16
+ export { default as ActionButton } from './components/ActionButton/ActionButton.svelte';
15
17
  export { default as Alert } from './components/Alert/Alert.svelte';
16
18
  export { default as AnnouncementBanner } from './components/AnnouncementBanner/AnnouncementBanner.svelte';
17
19
  export { default as AppShell } from './components/AppShell/AppShell.svelte';
20
+ export { default as AppShellBar } from './components/AppShell/AppShellBar.svelte';
18
21
  export { default as AppShellHeader } from './components/AppShell/AppShellHeader.svelte';
19
22
  export { default as AppShellSidebar } from './components/AppShell/AppShellSidebar.svelte';
20
23
  export { default as Avatar } from './components/Avatar/Avatar.svelte';
@@ -37,6 +40,12 @@ export { default as CommandPaletteProvider } from './components/CommandPalette/C
37
40
  export { default as ConfirmModal } from './components/ConfirmModal/ConfirmModal.svelte';
38
41
  export { default as Container } from './components/Container/Container.svelte';
39
42
  export { default as ContextMenuButton } from './components/ContextMenu/ContextMenuButton.svelte';
43
+ export { default as ControlBar } from './components/ControlBar/ControlBar.svelte';
44
+ export { default as ControlBarContent } from './components/ControlBar/ControlBarContent.svelte';
45
+ export { default as ControlBarDescription } from './components/ControlBar/ControlBarDescription.svelte';
46
+ export { default as ControlBarHeader } from './components/ControlBar/ControlBarHeader.svelte';
47
+ export { default as ControlBarOverflow } from './components/ControlBar/ControlBarOverflow.svelte';
48
+ export { default as ControlBarTitle } from './components/ControlBar/ControlBarTitle.svelte';
40
49
  export { default as DatePicker } from './components/DatePicker/DatePicker.svelte';
41
50
  export { default as Field } from './components/Field/Field.svelte';
42
51
  export { default as FormatBytes } from './components/FormatBytes/FormatBytes.svelte';
@@ -1,12 +1,9 @@
1
1
  <script lang="ts">
2
2
  import { ChildKey } from '../constants.js';
3
- import type { ChildData } from '../types.js';
3
+ import type { ContextType } from '../types.js';
4
4
  import { withPrefix } from '../utilities/internal.js';
5
- import { getContext, type Snippet } from 'svelte';
5
+ import { getContext, onDestroy, type Snippet } from 'svelte';
6
6
 
7
- type ContextType = {
8
- register: (key: ChildKey, data: () => ChildData) => void;
9
- };
10
7
  type Props = {
11
8
  for: ChildKey;
12
9
  as: ChildKey;
@@ -26,4 +23,8 @@
26
23
  } else {
27
24
  console.log('Unable to find context for key:', key);
28
25
  }
26
+
27
+ onDestroy(() => {
28
+ context?.unregister(as);
29
+ });
29
30
  </script>
@@ -3,6 +3,8 @@ declare const defaultTranslations: {
3
3
  cancel: string;
4
4
  close: string;
5
5
  confirm: string;
6
+ expand: string;
7
+ collapse: string;
6
8
  code_copy: string;
7
9
  code_copied: string;
8
10
  search_placeholder: string;
@@ -3,6 +3,8 @@ const defaultTranslations = {
3
3
  cancel: 'Cancel',
4
4
  close: 'Close',
5
5
  confirm: 'Confirm',
6
+ expand: 'Expand',
7
+ collapse: 'Collapse',
6
8
  // code
7
9
  code_copy: 'Copy',
8
10
  code_copied: 'Copied',
@@ -5,6 +5,11 @@
5
5
 
6
6
  @import 'tailwindcss';
7
7
 
8
+ @theme {
9
+ --spacing-control-bar-container: --spacing(18);
10
+ --spacing-control-bar: --spacing(16);
11
+ }
12
+
8
13
  @theme inline {
9
14
  --color-primary-50: var(--immich-ui-primary-50);
10
15
  --color-primary-100: var(--immich-ui-primary-100);
package/dist/types.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Shortcut } from './actions/shortcut.js';
2
+ import type { ChildKey } from './constants.js';
2
3
  import type { Translations } from './services/translation.svelte.js';
3
4
  import type { DateTime } from 'luxon';
4
5
  import type { Component, Snippet } from 'svelte';
@@ -29,6 +30,19 @@ export type IconLike = string | {
29
30
  };
30
31
  export type MaybeArray<T> = T | T[];
31
32
  export type MaybePromise<T> = T | Promise<T>;
33
+ export type NavbarVariant = 'compact';
34
+ export type NavbarProps = {
35
+ title: string;
36
+ href: string;
37
+ active?: boolean;
38
+ variant?: NavbarVariant;
39
+ isActive?: () => boolean;
40
+ icon?: string | IconProps;
41
+ activeIcon?: string | IconProps;
42
+ expanded?: boolean;
43
+ items?: NavbarProps[] | Snippet;
44
+ class?: string;
45
+ };
32
46
  export type IconProps = {
33
47
  icon: IconLike;
34
48
  title?: string;
@@ -291,4 +305,23 @@ export type CarouselImageItem = {
291
305
  alt?: string;
292
306
  id?: string;
293
307
  };
308
+ export type ControlBarProps = {
309
+ ref?: HTMLElement | null;
310
+ closeIcon?: IconLike | Snippet;
311
+ variant?: Variants;
312
+ shape?: 'semi-round' | 'rectangle';
313
+ translations?: TranslationProps<'close'>;
314
+ onClose?: () => void;
315
+ children?: Snippet;
316
+ closeOnEsc?: boolean;
317
+ static?: boolean;
318
+ } & HTMLAttributes<HTMLElement>;
319
+ export type ActionBarProps = ControlBarProps & {
320
+ actions: ActionItem[];
321
+ overflowActions?: ActionItem[];
322
+ };
323
+ export type ContextType = {
324
+ register: (key: ChildKey, data: () => ChildData) => void;
325
+ unregister: (key: ChildKey) => void;
326
+ };
294
327
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immich/ui",
3
- "version": "0.58.4",
3
+ "version": "0.60.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.12.0"
61
+ "node": "24.13.0"
62
62
  },
63
63
  "scripts": {
64
64
  "create": "node scripts/create.js",