@immich/ui 0.28.0 → 0.29.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.
@@ -16,6 +16,7 @@
16
16
  value = $bindable<string>(),
17
17
  leadingIcon,
18
18
  trailingIcon,
19
+ inputSize,
19
20
  ...restProps
20
21
  }: InputProps = $props();
21
22
 
@@ -119,6 +120,7 @@
119
120
  aria-disabled={disabled}
120
121
  aria-describedby={descriptionId}
121
122
  readonly={readOnly}
123
+ size={inputSize}
122
124
  aria-readonly={readOnly}
123
125
  class={cleanClass(
124
126
  inputStyles({
@@ -13,6 +13,10 @@
13
13
  size,
14
14
  ...props
15
15
  }: PasswordInputProps = $props();
16
+
17
+ let labelValue = $derived(
18
+ isVisible ? t('hide_password', translations) : t('show_password', translations),
19
+ );
16
20
  </script>
17
21
 
18
22
  <Input bind:value {size} type={isVisible ? 'text' : 'password'} {color} {...props}>
@@ -26,8 +30,8 @@
26
30
  class="me-1"
27
31
  icon={isVisible ? mdiEyeOffOutline : mdiEyeOutline}
28
32
  onclick={() => (isVisible = !isVisible)}
29
- title={isVisible ? t('hide_password', translations) : t('show_password', translations)}
30
- aria-label={t('show_password', translations)}
33
+ title={labelValue}
34
+ aria-label={labelValue}
31
35
  />
32
36
  {/if}
33
37
  {/snippet}
@@ -0,0 +1,120 @@
1
+ <script lang="ts">
2
+ import { getFieldContext } from '../../common/context.svelte.js';
3
+ import Label from '../Label/Label.svelte';
4
+ import Text from '../Text/Text.svelte';
5
+ import type { TextareaProps } from '../../types.js';
6
+ import { cleanClass, generateId } from '../../utils.js';
7
+ import type { FormEventHandler } from 'svelte/elements';
8
+ import { tv } from 'tailwind-variants';
9
+
10
+ let {
11
+ ref = $bindable(null),
12
+ containerRef = $bindable(null),
13
+ shape = 'semi-round',
14
+ size = 'medium',
15
+ class: className,
16
+ grow,
17
+ value = $bindable<string>(),
18
+ ...restProps
19
+ }: TextareaProps = $props();
20
+
21
+ const { label, description, readOnly, required, invalid, disabled, ...labelProps } =
22
+ $derived(getFieldContext());
23
+
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',
26
+ variants: {
27
+ shape: {
28
+ rectangle: 'rounded-none',
29
+ 'semi-round': '',
30
+ round: 'rounded-full',
31
+ },
32
+ padding: {
33
+ base: 'px-4 py-3',
34
+ round: 'px-5 py-3',
35
+ },
36
+ grow: {
37
+ true: 'resize-none',
38
+ false: '',
39
+ },
40
+ roundedSize: {
41
+ tiny: 'rounded-xl',
42
+ small: 'rounded-xl',
43
+ medium: 'rounded-2xl',
44
+ large: 'rounded-2xl',
45
+ giant: 'rounded-2xl',
46
+ },
47
+ textSize: {
48
+ tiny: 'text-xs',
49
+ small: 'text-sm',
50
+ medium: 'text-base',
51
+ large: 'text-lg',
52
+ giant: 'text-xl',
53
+ },
54
+ invalid: {
55
+ true: 'border-danger/80 border',
56
+ false: '',
57
+ },
58
+ },
59
+ });
60
+
61
+ const id = generateId();
62
+ const inputId = `input-${id}`;
63
+ const labelId = `label-${id}`;
64
+ const descriptionId = $derived(description ? `description-${id}` : undefined);
65
+
66
+ const onInput: FormEventHandler<HTMLTextAreaElement> = (event) => {
67
+ const element = event.target as HTMLTextAreaElement;
68
+ if (element && grow) {
69
+ element.style.height = 'auto';
70
+ element.style.height = `${element.scrollHeight}px`;
71
+ }
72
+
73
+ restProps?.oninput?.(event);
74
+ };
75
+ </script>
76
+
77
+ <div class="flex w-full flex-col gap-1" bind:this={containerRef}>
78
+ {#if label}
79
+ <Label id={labelId} for={inputId} {label} {...labelProps} />
80
+ {/if}
81
+
82
+ {#if description}
83
+ <Text color="secondary" size="small" id={descriptionId}>{description}</Text>
84
+ {/if}
85
+
86
+ <div class="relative w-full">
87
+ <textarea
88
+ oninput={onInput}
89
+ id={inputId}
90
+ aria-labelledby={label && labelId}
91
+ {required}
92
+ aria-required={required}
93
+ {disabled}
94
+ aria-disabled={disabled}
95
+ aria-describedby={descriptionId}
96
+ readonly={readOnly}
97
+ aria-readonly={readOnly}
98
+ class={cleanClass(
99
+ styles({
100
+ shape,
101
+ textSize: size,
102
+ padding: shape === 'round' ? 'round' : 'base',
103
+ grow,
104
+ roundedSize: shape === 'semi-round' ? size : undefined,
105
+ invalid,
106
+ }),
107
+ className,
108
+ )}
109
+ bind:this={ref}
110
+ bind:value
111
+ {...restProps}
112
+ ></textarea>
113
+ </div>
114
+ </div>
115
+
116
+ <style>
117
+ textarea::-ms-reveal {
118
+ display: none;
119
+ }
120
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { TextareaProps } from '../../types.js';
2
+ declare const Textarea: import("svelte").Component<TextareaProps, {}, "value" | "ref" | "containerRef">;
3
+ type Textarea = ReturnType<typeof Textarea>;
4
+ export default Textarea;
package/dist/index.d.ts CHANGED
@@ -56,6 +56,7 @@ export { default as VStack } from './components/Stack/VStack.svelte';
56
56
  export { default as SupporterBadge } from './components/SupporterBadge/SupporterBadge.svelte';
57
57
  export { default as Switch } from './components/Switch/Switch.svelte';
58
58
  export { default as Text } from './components/Text/Text.svelte';
59
+ export { default as Textarea } from './components/Textarea/Textarea.svelte';
59
60
  export { default as ThemeSwitcher } from './components/ThemeSwitcher/ThemeSwitcher.svelte';
60
61
  export * from './services/command-palette-manager.svelte.js';
61
62
  export * from './services/modal-manager.svelte.js';
package/dist/index.js CHANGED
@@ -58,6 +58,7 @@ export { default as VStack } from './components/Stack/VStack.svelte';
58
58
  export { default as SupporterBadge } from './components/SupporterBadge/SupporterBadge.svelte';
59
59
  export { default as Switch } from './components/Switch/Switch.svelte';
60
60
  export { default as Text } from './components/Text/Text.svelte';
61
+ export { default as Textarea } from './components/Textarea/Textarea.svelte';
61
62
  export { default as ThemeSwitcher } from './components/ThemeSwitcher/ThemeSwitcher.svelte';
62
63
  // helpers
63
64
  export * from './services/command-palette-manager.svelte.js';
package/dist/types.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Translations } from './services/translation.svelte.js';
2
2
  import type { Snippet } from 'svelte';
3
- import type { HTMLAnchorAttributes, HTMLButtonAttributes, HTMLInputAttributes, HTMLLabelAttributes } from 'svelte/elements';
3
+ import type { HTMLAnchorAttributes, HTMLButtonAttributes, HTMLInputAttributes, HTMLLabelAttributes, HTMLTextareaAttributes } from 'svelte/elements';
4
4
  export type Color = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info';
5
5
  export type TextColor = Color | 'muted';
6
6
  export type TextVariant = 'italic';
@@ -119,9 +119,19 @@ export type InputProps = BaseInputProps & {
119
119
  trailingIcon?: IconLike | Snippet;
120
120
  };
121
121
  export type PasswordInputProps = BaseInputProps & {
122
+ ref?: HTMLInputElement | null;
122
123
  translations?: TranslationProps<'show_password' | 'hide_password'>;
123
124
  isVisible?: boolean;
124
125
  };
126
+ export type TextareaProps = {
127
+ ref?: HTMLTextAreaElement | null;
128
+ containerRef?: HTMLElement | null;
129
+ class?: string;
130
+ value?: string;
131
+ size?: Size;
132
+ shape?: Shape;
133
+ grow?: boolean;
134
+ } & HTMLTextareaAttributes;
125
135
  export type SelectItem = {
126
136
  label?: string;
127
137
  value: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@immich/ui",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "license": "GNU Affero General Public License version 3",
5
5
  "scripts": {
6
6
  "create": "node scripts/create.js",