@immich/ui 0.28.1 → 0.30.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.
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { getFieldContext } from '../../common/context.svelte.js';
|
|
3
|
+
import Icon from '../Icon/Icon.svelte';
|
|
3
4
|
import Label from '../Label/Label.svelte';
|
|
4
5
|
import Text from '../Text/Text.svelte';
|
|
5
6
|
import type { InputProps } from '../../types.js';
|
|
6
7
|
import { cleanClass, generateId, isIconLike } from '../../utils.js';
|
|
7
|
-
import Icon from '../Icon/Icon.svelte';
|
|
8
8
|
import { tv } from 'tailwind-variants';
|
|
9
9
|
|
|
10
10
|
let {
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
value = $bindable<string>(),
|
|
17
17
|
leadingIcon,
|
|
18
18
|
trailingIcon,
|
|
19
|
+
trailingText,
|
|
20
|
+
inputSize,
|
|
19
21
|
...restProps
|
|
20
22
|
}: InputProps = $props();
|
|
21
23
|
|
|
@@ -23,7 +25,7 @@
|
|
|
23
25
|
$derived(getFieldContext());
|
|
24
26
|
|
|
25
27
|
const iconStyles = tv({
|
|
26
|
-
base: '
|
|
28
|
+
base: 'flex flex-shrink-0 items-center justify-center',
|
|
27
29
|
variants: {
|
|
28
30
|
size: {
|
|
29
31
|
tiny: 'w-6',
|
|
@@ -35,18 +37,14 @@
|
|
|
35
37
|
},
|
|
36
38
|
});
|
|
37
39
|
|
|
38
|
-
const
|
|
39
|
-
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',
|
|
40
|
+
const containerStyles = tv({
|
|
41
|
+
base: 'flex w-full items-center 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',
|
|
40
42
|
variants: {
|
|
41
43
|
shape: {
|
|
42
44
|
rectangle: 'rounded-none',
|
|
43
45
|
'semi-round': '',
|
|
44
46
|
round: 'rounded-full',
|
|
45
47
|
},
|
|
46
|
-
padding: {
|
|
47
|
-
base: 'px-4 py-3',
|
|
48
|
-
round: 'px-5 py-3',
|
|
49
|
-
},
|
|
50
48
|
roundedSize: {
|
|
51
49
|
tiny: 'rounded-xl',
|
|
52
50
|
small: 'rounded-xl',
|
|
@@ -54,21 +52,16 @@
|
|
|
54
52
|
large: 'rounded-2xl',
|
|
55
53
|
giant: 'rounded-2xl',
|
|
56
54
|
},
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
small: 'ps-8',
|
|
61
|
-
medium: 'ps-10',
|
|
62
|
-
large: 'ps-12',
|
|
63
|
-
giant: 'ps-14',
|
|
64
|
-
},
|
|
65
|
-
paddingRight: {
|
|
66
|
-
tiny: 'pe-6',
|
|
67
|
-
small: 'pe-8',
|
|
68
|
-
medium: 'pe-10',
|
|
69
|
-
large: 'pe-12',
|
|
70
|
-
giant: 'pe-14',
|
|
55
|
+
invalid: {
|
|
56
|
+
true: 'border-danger/80 border',
|
|
57
|
+
false: '',
|
|
71
58
|
},
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const inputStyles = tv({
|
|
63
|
+
base: 'flex-1 bg-transparent py-3 outline-none disabled:cursor-not-allowed',
|
|
64
|
+
variants: {
|
|
72
65
|
textSize: {
|
|
73
66
|
tiny: 'text-xs',
|
|
74
67
|
small: 'text-sm',
|
|
@@ -76,9 +69,22 @@
|
|
|
76
69
|
large: 'text-lg',
|
|
77
70
|
giant: 'text-xl',
|
|
78
71
|
},
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
leadingPadding: {
|
|
73
|
+
base: 'pl-4',
|
|
74
|
+
icon: 'pl-0',
|
|
75
|
+
},
|
|
76
|
+
trailingPadding: {
|
|
77
|
+
base: 'pr-4',
|
|
78
|
+
icon: 'pr-0',
|
|
79
|
+
},
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
const trailingTextStyles = tv({
|
|
84
|
+
variants: {
|
|
85
|
+
padding: {
|
|
86
|
+
base: 'px-4',
|
|
87
|
+
icon: 'pl-4',
|
|
82
88
|
},
|
|
83
89
|
},
|
|
84
90
|
});
|
|
@@ -98,18 +104,26 @@
|
|
|
98
104
|
<Text color="secondary" size="small" id={descriptionId}>{description}</Text>
|
|
99
105
|
{/if}
|
|
100
106
|
|
|
101
|
-
<div
|
|
107
|
+
<div
|
|
108
|
+
class={cleanClass(
|
|
109
|
+
containerStyles({
|
|
110
|
+
shape,
|
|
111
|
+
roundedSize: shape === 'semi-round' ? size : undefined,
|
|
112
|
+
invalid,
|
|
113
|
+
}),
|
|
114
|
+
className,
|
|
115
|
+
)}
|
|
116
|
+
>
|
|
102
117
|
{#if leadingIcon}
|
|
103
118
|
<div tabindex="-1" class={iconStyles({ size })}>
|
|
104
|
-
{#if leadingIcon}
|
|
105
|
-
{
|
|
106
|
-
|
|
107
|
-
{
|
|
108
|
-
{@render leadingIcon()}
|
|
109
|
-
{/if}
|
|
119
|
+
{#if isIconLike(leadingIcon)}
|
|
120
|
+
<Icon size="60%" icon={leadingIcon} />
|
|
121
|
+
{:else}
|
|
122
|
+
{@render leadingIcon()}
|
|
110
123
|
{/if}
|
|
111
124
|
</div>
|
|
112
125
|
{/if}
|
|
126
|
+
|
|
113
127
|
<input
|
|
114
128
|
id={inputId}
|
|
115
129
|
aria-labelledby={label && labelId}
|
|
@@ -119,31 +133,31 @@
|
|
|
119
133
|
aria-disabled={disabled}
|
|
120
134
|
aria-describedby={descriptionId}
|
|
121
135
|
readonly={readOnly}
|
|
136
|
+
size={inputSize}
|
|
122
137
|
aria-readonly={readOnly}
|
|
123
|
-
class={
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
paddingLeft: leadingIcon ? size : undefined,
|
|
129
|
-
paddingRight: trailingIcon ? size : undefined,
|
|
130
|
-
roundedSize: shape === 'semi-round' ? size : undefined,
|
|
131
|
-
invalid,
|
|
132
|
-
}),
|
|
133
|
-
className,
|
|
134
|
-
)}
|
|
138
|
+
class={inputStyles({
|
|
139
|
+
textSize: size,
|
|
140
|
+
leadingPadding: leadingIcon ? 'icon' : 'base',
|
|
141
|
+
trailingPadding: trailingIcon || trailingText ? 'icon' : 'base',
|
|
142
|
+
})}
|
|
135
143
|
bind:this={ref}
|
|
136
144
|
bind:value
|
|
137
145
|
{...restProps}
|
|
138
146
|
/>
|
|
147
|
+
{#if trailingText}
|
|
148
|
+
<Text
|
|
149
|
+
{size}
|
|
150
|
+
color="muted"
|
|
151
|
+
class={trailingTextStyles({ padding: trailingIcon ? 'icon' : 'base' })}>{trailingText}</Text
|
|
152
|
+
>
|
|
153
|
+
{/if}
|
|
154
|
+
|
|
139
155
|
{#if trailingIcon}
|
|
140
|
-
<div tabindex="-1" class={
|
|
141
|
-
{#if trailingIcon}
|
|
142
|
-
{
|
|
143
|
-
|
|
144
|
-
{
|
|
145
|
-
{@render trailingIcon()}
|
|
146
|
-
{/if}
|
|
156
|
+
<div tabindex="-1" class={iconStyles({ size })}>
|
|
157
|
+
{#if isIconLike(trailingIcon)}
|
|
158
|
+
<Icon size="60%" icon={trailingIcon} />
|
|
159
|
+
{:else}
|
|
160
|
+
{@render trailingIcon()}
|
|
147
161
|
{/if}
|
|
148
162
|
</div>
|
|
149
163
|
{/if}
|
|
@@ -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>
|
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';
|
|
@@ -117,11 +117,22 @@ export type InputProps = BaseInputProps & {
|
|
|
117
117
|
type?: HTMLInputAttributes['type'];
|
|
118
118
|
leadingIcon?: IconLike | Snippet;
|
|
119
119
|
trailingIcon?: IconLike | Snippet;
|
|
120
|
+
trailingText?: string;
|
|
120
121
|
};
|
|
121
122
|
export type PasswordInputProps = BaseInputProps & {
|
|
123
|
+
ref?: HTMLInputElement | null;
|
|
122
124
|
translations?: TranslationProps<'show_password' | 'hide_password'>;
|
|
123
125
|
isVisible?: boolean;
|
|
124
126
|
};
|
|
127
|
+
export type TextareaProps = {
|
|
128
|
+
ref?: HTMLTextAreaElement | null;
|
|
129
|
+
containerRef?: HTMLElement | null;
|
|
130
|
+
class?: string;
|
|
131
|
+
value?: string;
|
|
132
|
+
size?: Size;
|
|
133
|
+
shape?: Shape;
|
|
134
|
+
grow?: boolean;
|
|
135
|
+
} & HTMLTextareaAttributes;
|
|
125
136
|
export type SelectItem = {
|
|
126
137
|
label?: string;
|
|
127
138
|
value: string;
|