@immich/ui 0.65.2 → 0.67.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.
- package/dist/components/PinInput/PinInput.svelte +124 -0
- package/dist/components/PinInput/PinInput.svelte.d.ts +5 -0
- package/dist/components/Textarea/Textarea.svelte +59 -26
- package/dist/components/Toast/Toast.svelte +12 -2
- package/dist/components/Toast/ToastContainer.svelte +1 -1
- package/dist/components/Toast/ToastContent.svelte +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/dist/types.d.ts +16 -2
- package/package.json +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getFieldContext } from '../../common/context.svelte.js';
|
|
3
|
+
import Label from '../Label/Label.svelte';
|
|
4
|
+
import { styleVariants } from '../../styles.js';
|
|
5
|
+
import type { PinInputProps } from '../../types.js';
|
|
6
|
+
import { cleanClass } from '../../utilities/internal.js';
|
|
7
|
+
import { PinInput, REGEXP_ONLY_DIGITS } from 'bits-ui';
|
|
8
|
+
import { tv } from 'tailwind-variants';
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
shape = 'semi-round',
|
|
12
|
+
size: initialSize,
|
|
13
|
+
value = $bindable<string>(''),
|
|
14
|
+
length = 6,
|
|
15
|
+
password,
|
|
16
|
+
onComplete,
|
|
17
|
+
class: className,
|
|
18
|
+
...props
|
|
19
|
+
}: PinInputProps = $props();
|
|
20
|
+
|
|
21
|
+
const context = getFieldContext();
|
|
22
|
+
|
|
23
|
+
const { label, disabled, ...labelProps } = $derived(context());
|
|
24
|
+
const size = $derived(initialSize ?? labelProps.size ?? 'large');
|
|
25
|
+
|
|
26
|
+
const inputStyles = tv({
|
|
27
|
+
base: 'group-has-disabled:text-dark data-active:border-primary dark:data-active:border-primary flex items-center justify-center border-2 bg-gray-100 font-mono transition-all duration-75 group-has-disabled:bg-gray-300 data-active:border-3 dark:bg-gray-800 dark:group-not-has-disabled:border-gray-700 dark:group-has-disabled:bg-gray-900 dark:group-has-disabled:text-gray-200',
|
|
28
|
+
variants: {
|
|
29
|
+
shape: styleVariants.shape,
|
|
30
|
+
size: {
|
|
31
|
+
tiny: 'h-9 w-7',
|
|
32
|
+
small: 'h-10 w-8',
|
|
33
|
+
medium: 'h-11 w-9',
|
|
34
|
+
large: 'h-12 w-10',
|
|
35
|
+
giant: 'h-14 w-12',
|
|
36
|
+
},
|
|
37
|
+
textSize: styleVariants.textSize,
|
|
38
|
+
roundedSize: {
|
|
39
|
+
tiny: 'rounded-lg',
|
|
40
|
+
small: 'rounded-lg',
|
|
41
|
+
medium: 'rounded-xl',
|
|
42
|
+
large: 'rounded-xl',
|
|
43
|
+
giant: 'rounded-2xl',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const caretStyles = tv({
|
|
49
|
+
base: 'caret bg-dark h-1/2',
|
|
50
|
+
variants: {
|
|
51
|
+
size: {
|
|
52
|
+
tiny: 'w-px',
|
|
53
|
+
small: 'w-px',
|
|
54
|
+
medium: 'w-[1.5px]',
|
|
55
|
+
large: 'w-[1.5px]',
|
|
56
|
+
giant: 'w-0.5',
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const id = $props.id();
|
|
62
|
+
const inputId = `input-${id}`;
|
|
63
|
+
const labelId = `label-${id}`;
|
|
64
|
+
</script>
|
|
65
|
+
|
|
66
|
+
<div class={cleanClass('flex flex-col gap-1', className)}>
|
|
67
|
+
{#if label}
|
|
68
|
+
<Label id={labelId} for={inputId} {label} {...labelProps} {size} />
|
|
69
|
+
{/if}
|
|
70
|
+
|
|
71
|
+
<PinInput.Root
|
|
72
|
+
{inputId}
|
|
73
|
+
aria-labelledby={label && labelId}
|
|
74
|
+
{disabled}
|
|
75
|
+
aria-disabled={disabled}
|
|
76
|
+
class="group flex w-fit items-center gap-2"
|
|
77
|
+
maxlength={length}
|
|
78
|
+
pattern={REGEXP_ONLY_DIGITS}
|
|
79
|
+
type={password ? 'password' : 'text'}
|
|
80
|
+
{onComplete}
|
|
81
|
+
bind:value
|
|
82
|
+
{...props}
|
|
83
|
+
>
|
|
84
|
+
{#snippet children({ cells })}
|
|
85
|
+
{#each cells as cell, i (i)}
|
|
86
|
+
<PinInput.Cell
|
|
87
|
+
{cell}
|
|
88
|
+
class={inputStyles({
|
|
89
|
+
shape,
|
|
90
|
+
size,
|
|
91
|
+
textSize: size,
|
|
92
|
+
roundedSize: shape === 'semi-round' ? size : undefined,
|
|
93
|
+
})}
|
|
94
|
+
>
|
|
95
|
+
{#if cell.char !== null}
|
|
96
|
+
<div>
|
|
97
|
+
{password ? '●' : cell.char}
|
|
98
|
+
</div>
|
|
99
|
+
{/if}
|
|
100
|
+
{#if cell.hasFakeCaret}
|
|
101
|
+
<div class="absolute flex h-full items-center justify-center">
|
|
102
|
+
<div class={caretStyles({ size })}></div>
|
|
103
|
+
</div>
|
|
104
|
+
{/if}
|
|
105
|
+
</PinInput.Cell>
|
|
106
|
+
{/each}
|
|
107
|
+
{/snippet}
|
|
108
|
+
</PinInput.Root>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<style>
|
|
112
|
+
.caret {
|
|
113
|
+
animation: blink 1.5s step-end infinite;
|
|
114
|
+
}
|
|
115
|
+
@keyframes blink {
|
|
116
|
+
0%,
|
|
117
|
+
100% {
|
|
118
|
+
opacity: 1;
|
|
119
|
+
}
|
|
120
|
+
50% {
|
|
121
|
+
opacity: 0;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
</style>
|
|
@@ -12,8 +12,10 @@
|
|
|
12
12
|
containerRef = $bindable(null),
|
|
13
13
|
shape = 'semi-round',
|
|
14
14
|
size: initialSize,
|
|
15
|
+
variant = 'input',
|
|
15
16
|
class: className,
|
|
16
|
-
|
|
17
|
+
rows = variant === 'ghost' ? 1 : undefined,
|
|
18
|
+
grow = variant === 'ghost',
|
|
17
19
|
value = $bindable<string>(),
|
|
18
20
|
...restProps
|
|
19
21
|
}: TextareaProps = $props();
|
|
@@ -22,18 +24,35 @@
|
|
|
22
24
|
const { label, description, readOnly, required, invalid, disabled, ...labelProps } = $derived(context());
|
|
23
25
|
const size = $derived(initialSize ?? labelProps.size ?? 'small');
|
|
24
26
|
|
|
25
|
-
const
|
|
26
|
-
base: '
|
|
27
|
+
const commonStyles = tv({
|
|
28
|
+
base: 'w-full outline-none disabled:cursor-not-allowed',
|
|
29
|
+
variants: {
|
|
30
|
+
textSize: styleVariants.textSize,
|
|
31
|
+
grow: { true: 'resize-none', false: '' },
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const ghostStyles = tv({
|
|
36
|
+
base: 'pb-2',
|
|
37
|
+
variants: {
|
|
38
|
+
state: {
|
|
39
|
+
active:
|
|
40
|
+
'hover:border-b-light-200 focus-within:hover:border-primary focus-within:border-primary focus-within:border-b-2 hover:border-b-2',
|
|
41
|
+
error: 'border-b-danger border-b-2',
|
|
42
|
+
disabled: '',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const inputStyles = tv({
|
|
48
|
+
base: 'focus-within:ring-primary dark:focus-within:ring-primary bg-gray-100 ring-1 ring-gray-200 focus-within:ring-1 disabled:bg-gray-300 disabled:text-gray-400 dark:bg-gray-800 dark:ring-black dark:disabled:bg-gray-800 dark:disabled:text-gray-200',
|
|
27
49
|
variants: {
|
|
28
50
|
shape: styleVariants.shape,
|
|
29
51
|
padding: {
|
|
30
52
|
base: 'px-4 py-3',
|
|
31
53
|
round: 'px-5 py-3',
|
|
32
54
|
},
|
|
33
|
-
|
|
34
|
-
true: 'resize-none',
|
|
35
|
-
false: '',
|
|
36
|
-
},
|
|
55
|
+
invalid: { true: 'border-danger/80 border', false: '' },
|
|
37
56
|
roundedSize: {
|
|
38
57
|
tiny: 'rounded-xl',
|
|
39
58
|
small: 'rounded-xl',
|
|
@@ -41,11 +60,6 @@
|
|
|
41
60
|
large: 'rounded-2xl',
|
|
42
61
|
giant: 'rounded-2xl',
|
|
43
62
|
},
|
|
44
|
-
textSize: styleVariants.textSize,
|
|
45
|
-
invalid: {
|
|
46
|
-
true: 'border-danger/80 border',
|
|
47
|
-
false: '',
|
|
48
|
-
},
|
|
49
63
|
},
|
|
50
64
|
});
|
|
51
65
|
|
|
@@ -54,21 +68,38 @@
|
|
|
54
68
|
const labelId = `label-${id}`;
|
|
55
69
|
const descriptionId = $derived(description ? `description-${id}` : undefined);
|
|
56
70
|
|
|
71
|
+
const parseStyleNumber = (raw: string) => {
|
|
72
|
+
if (raw === 'none') {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const value = Number.parseFloat(raw);
|
|
77
|
+
if (Number.isNaN(value)) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return value;
|
|
82
|
+
};
|
|
83
|
+
|
|
57
84
|
const autogrow = (element: HTMLTextAreaElement | null) => {
|
|
58
85
|
if (element && grow) {
|
|
59
86
|
element.style.minHeight = '0';
|
|
60
87
|
element.style.height = 'auto';
|
|
61
|
-
|
|
88
|
+
|
|
89
|
+
const style = getComputedStyle(element);
|
|
90
|
+
const borderTopWidth = parseStyleNumber(style.borderTopWidth) ?? 0;
|
|
91
|
+
const borderBottomWidth = parseStyleNumber(style.borderBottomWidth) ?? 0;
|
|
92
|
+
const height = element.scrollHeight + borderTopWidth + borderBottomWidth;
|
|
93
|
+
|
|
94
|
+
element.style.height = `${height}px`;
|
|
62
95
|
|
|
63
96
|
// Show scrollbar only if there is a max-height and content exceeds it
|
|
64
|
-
const maxHeight =
|
|
97
|
+
const maxHeight = parseStyleNumber(style.maxHeight);
|
|
65
98
|
const hasMaxHeight = maxHeight !== undefined;
|
|
66
|
-
if (hasMaxHeight &&
|
|
99
|
+
if (hasMaxHeight && height > maxHeight) {
|
|
67
100
|
element.style.overflow = 'auto';
|
|
68
|
-
} else if (hasMaxHeight && element.scrollHeight <= maxHeight) {
|
|
69
|
-
element.style.overflow = 'hidden';
|
|
70
101
|
} else {
|
|
71
|
-
element.style.overflow = '';
|
|
102
|
+
element.style.overflow = 'hidden';
|
|
72
103
|
}
|
|
73
104
|
}
|
|
74
105
|
};
|
|
@@ -99,15 +130,17 @@
|
|
|
99
130
|
aria-describedby={descriptionId}
|
|
100
131
|
readonly={readOnly}
|
|
101
132
|
aria-readonly={readOnly}
|
|
133
|
+
{rows}
|
|
102
134
|
class={cleanClass(
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
135
|
+
commonStyles({ textSize: size, grow }),
|
|
136
|
+
variant === 'input' &&
|
|
137
|
+
inputStyles({
|
|
138
|
+
shape,
|
|
139
|
+
invalid,
|
|
140
|
+
padding: shape === 'round' ? 'round' : 'base',
|
|
141
|
+
roundedSize: shape === 'semi-round' ? size : undefined,
|
|
142
|
+
}),
|
|
143
|
+
variant === 'ghost' && ghostStyles({ state: invalid ? 'error' : disabled || readOnly ? 'disabled' : 'active' }),
|
|
111
144
|
className,
|
|
112
145
|
)}
|
|
113
146
|
bind:this={ref}
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import Button from '../Button/Button.svelte';
|
|
2
3
|
import ToastContainer from './ToastContainer.svelte';
|
|
3
4
|
import ToastContent from './ToastContent.svelte';
|
|
4
5
|
import type { ToastProps } from '../../types.js';
|
|
5
6
|
|
|
6
|
-
let { children, title, description, icon, onClose, ...props }: ToastProps = $props();
|
|
7
|
+
let { children, title, description, icon, onClose, button, ...props }: ToastProps = $props();
|
|
7
8
|
</script>
|
|
8
9
|
|
|
9
10
|
<ToastContainer {...props}>
|
|
10
11
|
{#if children}
|
|
11
12
|
{@render children()}
|
|
12
13
|
{:else if title}
|
|
13
|
-
<ToastContent {title} {description} {icon} {onClose} {...props}
|
|
14
|
+
<ToastContent {title} {description} {icon} {onClose} {...props}>
|
|
15
|
+
{#if button}
|
|
16
|
+
{@const { label, ...rest } = button}
|
|
17
|
+
<div class="flex justify-end px-3 pt-2">
|
|
18
|
+
<Button color="secondary" size="small" {...rest}>
|
|
19
|
+
{label}
|
|
20
|
+
</Button>
|
|
21
|
+
</div>
|
|
22
|
+
{/if}
|
|
23
|
+
</ToastContent>
|
|
14
24
|
{/if}
|
|
15
25
|
</ToastContainer>
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
}: ToastContainerProps = $props();
|
|
16
16
|
|
|
17
17
|
const containerStyles = tv({
|
|
18
|
-
base: 'bg-light text-dark overflow-hidden border py-
|
|
18
|
+
base: 'bg-light text-dark overflow-hidden border py-3 shadow-xs transition-all',
|
|
19
19
|
variants: {
|
|
20
20
|
color: {
|
|
21
21
|
primary: 'border-primary-100 bg-primary-50 dark:bg-primary-100 dark:border-primary-200',
|
package/dist/index.d.ts
CHANGED
|
@@ -70,6 +70,7 @@ export { default as NavbarGroup } from './components/Navbar/NavbarGroup.svelte';
|
|
|
70
70
|
export { default as NavbarItem } from './components/Navbar/NavbarItem.svelte';
|
|
71
71
|
export { default as NumberInput } from './components/NumberInput/NumberInput.svelte';
|
|
72
72
|
export { default as PasswordInput } from './components/PasswordInput/PasswordInput.svelte';
|
|
73
|
+
export { default as PinInput } from './components/PinInput/PinInput.svelte';
|
|
73
74
|
export { default as ProgressBar } from './components/ProgressBar/ProgressBar.svelte';
|
|
74
75
|
export { default as Scrollable } from './components/Scrollable/Scrollable.svelte';
|
|
75
76
|
export { default as Select } from './components/Select/Select.svelte';
|
|
@@ -103,9 +104,9 @@ export * from './services/theme.svelte.js';
|
|
|
103
104
|
export * from './services/toast-manager.svelte.js';
|
|
104
105
|
export * from './services/translation.svelte.js';
|
|
105
106
|
export * from './state/locale-state.svelte.js';
|
|
107
|
+
export { isModalOpen } from './state/modal-state.svelte.js';
|
|
106
108
|
export * from './types.js';
|
|
107
109
|
export * from './utilities/byte-units.js';
|
|
108
110
|
export * from './utilities/common.js';
|
|
109
|
-
export { isModalOpen } from './state/modal-state.svelte.js';
|
|
110
111
|
export * from './site/constants.js';
|
|
111
112
|
export { default as SiteFooter } from './site/SiteFooter.svelte';
|
package/dist/index.js
CHANGED
|
@@ -72,6 +72,7 @@ export { default as NavbarGroup } from './components/Navbar/NavbarGroup.svelte';
|
|
|
72
72
|
export { default as NavbarItem } from './components/Navbar/NavbarItem.svelte';
|
|
73
73
|
export { default as NumberInput } from './components/NumberInput/NumberInput.svelte';
|
|
74
74
|
export { default as PasswordInput } from './components/PasswordInput/PasswordInput.svelte';
|
|
75
|
+
export { default as PinInput } from './components/PinInput/PinInput.svelte';
|
|
75
76
|
export { default as ProgressBar } from './components/ProgressBar/ProgressBar.svelte';
|
|
76
77
|
export { default as Scrollable } from './components/Scrollable/Scrollable.svelte';
|
|
77
78
|
export { default as Select } from './components/Select/Select.svelte';
|
|
@@ -106,10 +107,10 @@ export * from './services/theme.svelte.js';
|
|
|
106
107
|
export * from './services/toast-manager.svelte.js';
|
|
107
108
|
export * from './services/translation.svelte.js';
|
|
108
109
|
export * from './state/locale-state.svelte.js';
|
|
110
|
+
export { isModalOpen } from './state/modal-state.svelte.js';
|
|
109
111
|
export * from './types.js';
|
|
110
112
|
export * from './utilities/byte-units.js';
|
|
111
113
|
export * from './utilities/common.js';
|
|
112
|
-
export { isModalOpen } from './state/modal-state.svelte.js';
|
|
113
114
|
// site
|
|
114
115
|
export * from './site/constants.js';
|
|
115
116
|
export { default as SiteFooter } from './site/SiteFooter.svelte';
|
package/dist/types.d.ts
CHANGED
|
@@ -156,9 +156,21 @@ export type PasswordInputProps = BaseInputProps<string> & {
|
|
|
156
156
|
translations?: TranslationProps<'show_password' | 'hide_password'>;
|
|
157
157
|
isVisible?: boolean;
|
|
158
158
|
};
|
|
159
|
+
export type PinInputProps = {
|
|
160
|
+
ref?: HTMLInputElement | null;
|
|
161
|
+
class?: string;
|
|
162
|
+
size?: Size;
|
|
163
|
+
value?: string;
|
|
164
|
+
shape?: Shape;
|
|
165
|
+
disabled?: boolean;
|
|
166
|
+
length?: number;
|
|
167
|
+
password?: boolean;
|
|
168
|
+
onComplete?: (value: string) => void;
|
|
169
|
+
};
|
|
159
170
|
export type TextareaProps = {
|
|
160
171
|
ref?: HTMLTextAreaElement | null;
|
|
161
172
|
containerRef?: HTMLElement | null;
|
|
173
|
+
variant?: 'input' | 'ghost';
|
|
162
174
|
class?: string;
|
|
163
175
|
value?: string;
|
|
164
176
|
size?: Size;
|
|
@@ -187,7 +199,7 @@ export type MultiSelectProps<T extends string> = SelectCommonProps<T> & {
|
|
|
187
199
|
onChange?: (values: T[]) => void;
|
|
188
200
|
onSelect?: (options: SelectOption<T>[]) => void;
|
|
189
201
|
};
|
|
190
|
-
export type
|
|
202
|
+
export type ToastWithId = ToastItem & {
|
|
191
203
|
id: string;
|
|
192
204
|
};
|
|
193
205
|
type ToastCommonProps = {
|
|
@@ -199,13 +211,14 @@ export type ToastContentProps = ToastCommonProps & {
|
|
|
199
211
|
icon?: IconLike | false;
|
|
200
212
|
onClose?: () => void;
|
|
201
213
|
children?: Snippet;
|
|
214
|
+
button?: ToastButton;
|
|
202
215
|
};
|
|
203
216
|
export type ToastContainerProps = ToastCommonProps & {
|
|
204
217
|
shape?: Shape;
|
|
205
218
|
size?: ContainerSize;
|
|
206
219
|
} & Omit<HTMLAttributes<HTMLElement>, 'title' | 'color' | 'size'>;
|
|
207
220
|
export type ToastPanelProps = {
|
|
208
|
-
items: Array<
|
|
221
|
+
items: Array<ToastWithId>;
|
|
209
222
|
} & HTMLAttributes<HTMLDivElement>;
|
|
210
223
|
export type ToastProps = ToastContentProps & ToastContainerProps;
|
|
211
224
|
type Closable = {
|
|
@@ -222,6 +235,7 @@ export type ToastShow = {
|
|
|
222
235
|
shape?: Shape;
|
|
223
236
|
icon?: IconLike | false;
|
|
224
237
|
size?: ContainerSize;
|
|
238
|
+
button?: ToastButton;
|
|
225
239
|
};
|
|
226
240
|
export type ToastOptions = {
|
|
227
241
|
id?: string;
|