@immich/ui 0.5.0 → 0.7.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/README.md +39 -45
- package/dist/common/context.svelte.d.ts +4 -0
- package/dist/common/context.svelte.js +6 -0
- package/dist/common/use-child.svelte.d.ts +5 -0
- package/dist/common/{use-context.svelte.js → use-child.svelte.js} +1 -1
- package/dist/components/Alert/Alert.svelte +8 -4
- package/dist/components/Alert/Alert.svelte.d.ts +2 -2
- package/dist/components/Card/Card.svelte +6 -6
- package/dist/components/Card/CardBody.svelte +3 -3
- package/dist/components/Card/CardFooter.svelte +2 -2
- package/dist/components/Card/CardHeader.svelte +2 -2
- package/dist/components/Form/Checkbox.svelte +4 -4
- package/dist/components/Form/Checkbox.svelte.d.ts +1 -1
- package/dist/components/Form/Field.svelte +29 -0
- package/dist/components/Form/Field.svelte.d.ts +6 -0
- package/dist/components/Form/HelperText.svelte +24 -0
- package/dist/components/Form/HelperText.svelte.d.ts +8 -0
- package/dist/components/Form/Input.svelte +109 -15
- package/dist/components/Form/Input.svelte.d.ts +2 -3
- package/dist/components/Form/PasswordInput.svelte +29 -0
- package/dist/components/Form/PasswordInput.svelte.d.ts +3 -0
- package/dist/components/LoadingSpinner/LoadingSpinner.svelte +54 -0
- package/dist/components/LoadingSpinner/LoadingSpinner.svelte.d.ts +7 -0
- package/dist/components/Stack/HStack.svelte +1 -1
- package/dist/components/Stack/HStack.svelte.d.ts +2 -1
- package/dist/components/Stack/Stack.svelte +0 -10
- package/dist/components/Stack/VStack.svelte +1 -1
- package/dist/components/Stack/VStack.svelte.d.ts +2 -1
- package/dist/components/Text/Text.svelte +1 -1
- package/dist/components/Text/Text.svelte.d.ts +1 -1
- package/dist/constants.d.ts +3 -1
- package/dist/constants.js +9 -7
- package/dist/index.d.ts +5 -1
- package/dist/index.js +5 -1
- package/dist/internal/Button.svelte +28 -7
- package/dist/internal/Child.svelte +4 -4
- package/dist/internal/Child.svelte.d.ts +3 -3
- package/dist/types.d.ts +28 -2
- package/dist/utils.d.ts +2 -2
- package/dist/utils.js +2 -0
- package/package.json +2 -2
- package/dist/common/use-context.svelte.d.ts +0 -5
package/README.md
CHANGED
|
@@ -1,58 +1,52 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @immich/ui
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A component library for [Immich](https://immich.app), written in [Svelte](https://svelte.dev).
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
## Creating a project
|
|
8
|
-
|
|
9
|
-
If you're seeing this, you've probably already done this step. Congrats!
|
|
5
|
+
## Install
|
|
10
6
|
|
|
11
7
|
```bash
|
|
12
|
-
|
|
13
|
-
npx sv create
|
|
14
|
-
|
|
15
|
-
# create a new project in my-app
|
|
16
|
-
npx sv create my-app
|
|
8
|
+
npm i -D @immich/ui
|
|
17
9
|
```
|
|
18
10
|
|
|
19
|
-
##
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Import components from `@immich/ui`. For example:
|
|
14
|
+
|
|
15
|
+
```html
|
|
16
|
+
<script lang="ts">
|
|
17
|
+
import {
|
|
18
|
+
Card,
|
|
19
|
+
CardBody,
|
|
20
|
+
CardHeader,
|
|
21
|
+
CardTitle,
|
|
22
|
+
CardDescription,
|
|
23
|
+
Heading,
|
|
24
|
+
Text,
|
|
25
|
+
} from '@immich/ui';
|
|
26
|
+
</script>
|
|
27
|
+
|
|
28
|
+
<Card>
|
|
29
|
+
<CardHeader>
|
|
30
|
+
<CardTitle>@immich/ui</CardTitle>
|
|
31
|
+
<CardDescription>A component library</CardDescription>
|
|
32
|
+
</CardHeader>
|
|
33
|
+
<CardBody>
|
|
34
|
+
<Lorem />
|
|
35
|
+
</CardBody>
|
|
36
|
+
<CardFooter>Privacy should not be a luxury</CardFooter>
|
|
37
|
+
</Card>
|
|
28
38
|
```
|
|
29
39
|
|
|
30
|
-
|
|
40
|
+
## Documentation
|
|
31
41
|
|
|
32
|
-
|
|
42
|
+
To view the examples located at `src/routes/examples`, run `npm start` and navigate to http://localhost:5173/.
|
|
33
43
|
|
|
34
|
-
|
|
44
|
+
## Contributing
|
|
35
45
|
|
|
36
|
-
|
|
37
|
-
npm run package
|
|
38
|
-
```
|
|
46
|
+
PR's are welcome! Also feel free to reach out to the team on [Discord](https://discord.immich.app).
|
|
39
47
|
|
|
40
|
-
|
|
48
|
+
## Technology
|
|
41
49
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
You can preview the production build with `npm run preview`.
|
|
47
|
-
|
|
48
|
-
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
|
49
|
-
|
|
50
|
-
## Publishing
|
|
51
|
-
|
|
52
|
-
Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
|
|
53
|
-
|
|
54
|
-
To publish your library to [npm](https://www.npmjs.com):
|
|
55
|
-
|
|
56
|
-
```bash
|
|
57
|
-
npm publish
|
|
58
|
-
```
|
|
50
|
+
- [Svelte](https://svelte.dev)
|
|
51
|
+
- [tailwindcss](https://tailwindcss.com)
|
|
52
|
+
- [Material Design icons (@mdi/js)](https://pictogrammers.com/library/mdi/)
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { withPrefix } from '../utils.js';
|
|
2
|
+
import { getContext, hasContext, setContext } from 'svelte';
|
|
3
|
+
const fieldKey = Symbol(withPrefix('field'));
|
|
4
|
+
export const setFieldContext = (field) => setContext(fieldKey, field);
|
|
5
|
+
export const hasFieldContext = () => hasContext(fieldKey);
|
|
6
|
+
export const getFieldContext = () => (getContext(fieldKey) || {});
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
|
|
15
15
|
type Props = {
|
|
16
16
|
color?: Color;
|
|
17
|
-
icon?: string;
|
|
18
|
-
title
|
|
17
|
+
icon?: string | false;
|
|
18
|
+
title?: string;
|
|
19
19
|
children?: Snippet;
|
|
20
20
|
};
|
|
21
21
|
|
|
@@ -27,7 +27,9 @@
|
|
|
27
27
|
danger: mdiCloseCircleOutline,
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
const icon = $derived(
|
|
30
|
+
const icon = $derived(
|
|
31
|
+
iconOverride === false ? undefined : iconOverride || (icons[color] ?? mdiInformationOutline),
|
|
32
|
+
);
|
|
31
33
|
</script>
|
|
32
34
|
|
|
33
35
|
<Card {color} variant="subtle">
|
|
@@ -40,7 +42,9 @@
|
|
|
40
42
|
{/if}
|
|
41
43
|
|
|
42
44
|
<div class="flex flex-col gap-2">
|
|
43
|
-
|
|
45
|
+
{#if title}
|
|
46
|
+
<Text size="large" fontWeight={children ? 'bold' : undefined}>{title}</Text>
|
|
47
|
+
{/if}
|
|
44
48
|
{#if children}
|
|
45
49
|
{@render children()}
|
|
46
50
|
{/if}
|
|
@@ -2,8 +2,8 @@ import type { Color } from '../../types.js';
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
declare const Alert: import("svelte").Component<{
|
|
4
4
|
color?: Color;
|
|
5
|
-
icon?: string;
|
|
6
|
-
title
|
|
5
|
+
icon?: string | false;
|
|
6
|
+
title?: string;
|
|
7
7
|
children?: Snippet;
|
|
8
8
|
}, {}, "">;
|
|
9
9
|
export default Alert;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import { withChildrenSnippets } from '../../common/use-
|
|
2
|
+
import { withChildrenSnippets } from '../../common/use-child.svelte.js';
|
|
3
3
|
import IconButton from '../IconButton/IconButton.svelte';
|
|
4
|
-
import {
|
|
4
|
+
import { ChildKey } from '../../constants.js';
|
|
5
5
|
import type { Color, Shape } from '../../types.js';
|
|
6
6
|
import { cleanClass } from '../../utils.js';
|
|
7
7
|
import { mdiChevronDown } from '@mdi/js';
|
|
@@ -104,10 +104,10 @@
|
|
|
104
104
|
expanded = !expanded;
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
-
const { getChildren: getChildSnippet } = withChildrenSnippets(
|
|
108
|
-
const headerChildren = $derived(getChildSnippet(
|
|
109
|
-
const bodyChildren = $derived(getChildSnippet(
|
|
110
|
-
const footerChildren = $derived(getChildSnippet(
|
|
107
|
+
const { getChildren: getChildSnippet } = withChildrenSnippets(ChildKey.Card);
|
|
108
|
+
const headerChildren = $derived(getChildSnippet(ChildKey.CardHeader));
|
|
109
|
+
const bodyChildren = $derived(getChildSnippet(ChildKey.CardBody));
|
|
110
|
+
const footerChildren = $derived(getChildSnippet(ChildKey.CardFooter));
|
|
111
111
|
|
|
112
112
|
const headerClasses = 'flex flex-col space-y-1.5';
|
|
113
113
|
const headerContainerClasses = $derived(
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { ChildKey } from '../../constants.js';
|
|
3
3
|
import Child from '../../internal/Child.svelte';
|
|
4
4
|
import { cleanClass } from '../../utils.js';
|
|
5
5
|
import type { Snippet } from 'svelte';
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
let { class: className, children }: Props = $props();
|
|
14
14
|
</script>
|
|
15
15
|
|
|
16
|
-
<Child for={
|
|
17
|
-
<div class={twMerge(cleanClass('p-4', className))}>
|
|
16
|
+
<Child for={ChildKey.Card} as={ChildKey.CardBody}>
|
|
17
|
+
<div class={twMerge(cleanClass('w-full p-4', className))}>
|
|
18
18
|
{@render children?.()}
|
|
19
19
|
</div>
|
|
20
20
|
</Child>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { ChildKey } from '../../constants.js';
|
|
3
3
|
import Child from '../../internal/Child.svelte';
|
|
4
4
|
import type { Snippet } from 'svelte';
|
|
5
5
|
|
|
@@ -10,6 +10,6 @@
|
|
|
10
10
|
let { children }: Props = $props();
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
|
-
<Child for={
|
|
13
|
+
<Child for={ChildKey.Card} as={ChildKey.CardFooter}>
|
|
14
14
|
{@render children?.()}
|
|
15
15
|
</Child>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { ChildKey } from '../../constants.js';
|
|
3
3
|
import Child from '../../internal/Child.svelte';
|
|
4
4
|
import type { Snippet } from 'svelte';
|
|
5
5
|
|
|
@@ -10,6 +10,6 @@
|
|
|
10
10
|
let { children }: Props = $props();
|
|
11
11
|
</script>
|
|
12
12
|
|
|
13
|
-
<Child for={
|
|
13
|
+
<Child for={ChildKey.Card} as={ChildKey.CardHeader}>
|
|
14
14
|
{@render children?.()}
|
|
15
15
|
</Child>
|
|
@@ -81,12 +81,12 @@
|
|
|
81
81
|
bind:checked
|
|
82
82
|
{...restProps}
|
|
83
83
|
>
|
|
84
|
-
{#snippet children({ checked })}
|
|
84
|
+
{#snippet children({ checked, indeterminate })}
|
|
85
85
|
<div class={cleanClass('flex items-center justify-center text-current')}>
|
|
86
|
-
{#if
|
|
87
|
-
<Icon icon={mdiCheck} size="100%" class={cleanClass(icon({ color }))} />
|
|
88
|
-
{:else if checked === 'indeterminate'}
|
|
86
|
+
{#if indeterminate}
|
|
89
87
|
<Icon icon={mdiMinus} size="100%" class={cleanClass(icon({ color }))} />
|
|
88
|
+
{:else if checked}
|
|
89
|
+
<Icon icon={mdiCheck} size="100%" class={cleanClass(icon({ color }))} />
|
|
90
90
|
{/if}
|
|
91
91
|
</div>
|
|
92
92
|
{/snippet}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { setFieldContext } from '../../common/context.svelte.js';
|
|
3
|
+
import { withChildrenSnippets } from '../../common/use-child.svelte.js';
|
|
4
|
+
import { ChildKey } from '../../constants.js';
|
|
5
|
+
import type { FieldContext } from '../../types.js';
|
|
6
|
+
import { type Snippet } from 'svelte';
|
|
7
|
+
|
|
8
|
+
type Props = FieldContext & {
|
|
9
|
+
children: Snippet;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const { children, ...props }: Props = $props();
|
|
13
|
+
|
|
14
|
+
const state = $state(props);
|
|
15
|
+
|
|
16
|
+
setFieldContext(state);
|
|
17
|
+
|
|
18
|
+
const { getChildren: getChildSnippet } = withChildrenSnippets(ChildKey.Field);
|
|
19
|
+
const helperTextChildren = $derived(getChildSnippet(ChildKey.HelperText));
|
|
20
|
+
</script>
|
|
21
|
+
|
|
22
|
+
<div>
|
|
23
|
+
{@render children()}
|
|
24
|
+
{#if helperTextChildren}
|
|
25
|
+
<div class="pt-1">
|
|
26
|
+
{@render helperTextChildren?.()}
|
|
27
|
+
</div>
|
|
28
|
+
{/if}
|
|
29
|
+
</div>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import Text from '../Text/Text.svelte';
|
|
3
|
+
import { ChildKey } from '../../constants.js';
|
|
4
|
+
import Child from '../../internal/Child.svelte';
|
|
5
|
+
import type { Color } from '../../types.js';
|
|
6
|
+
import { cleanClass } from '../../utils.js';
|
|
7
|
+
import type { Snippet } from 'svelte';
|
|
8
|
+
|
|
9
|
+
type Props = {
|
|
10
|
+
color?: Color;
|
|
11
|
+
class?: string;
|
|
12
|
+
children?: Snippet;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
let { class: className, children, color }: Props = $props();
|
|
16
|
+
</script>
|
|
17
|
+
|
|
18
|
+
<Child for={ChildKey.Field} as={ChildKey.HelperText}>
|
|
19
|
+
<div class={cleanClass(className)}>
|
|
20
|
+
<Text {color} size="small">
|
|
21
|
+
{@render children?.()}
|
|
22
|
+
</Text>
|
|
23
|
+
</div>
|
|
24
|
+
</Child>
|
|
@@ -1,22 +1,116 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import
|
|
3
|
-
import type {
|
|
4
|
-
import { cleanClass } from '../../utils.js';
|
|
2
|
+
import { getFieldContext } from '../../common/context.svelte.js';
|
|
3
|
+
import type { InputProps } from '../../types.js';
|
|
4
|
+
import { cleanClass, generateId } from '../../utils.js';
|
|
5
|
+
import { tv } from 'tailwind-variants';
|
|
5
6
|
|
|
6
7
|
let {
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
shape = 'semi-round',
|
|
9
|
+
size = 'medium',
|
|
9
10
|
class: className,
|
|
11
|
+
value = $bindable<string>(),
|
|
12
|
+
trailingIcon,
|
|
10
13
|
...restProps
|
|
11
|
-
}:
|
|
14
|
+
}: InputProps = $props();
|
|
15
|
+
|
|
16
|
+
const {
|
|
17
|
+
label,
|
|
18
|
+
readOnly = false,
|
|
19
|
+
required = false,
|
|
20
|
+
invalid = false,
|
|
21
|
+
disabled = false,
|
|
22
|
+
} = $derived(getFieldContext());
|
|
23
|
+
|
|
24
|
+
const labelStyles = tv({
|
|
25
|
+
base: '',
|
|
26
|
+
variants: {
|
|
27
|
+
size: {
|
|
28
|
+
tiny: 'text-xs',
|
|
29
|
+
small: 'text-sm',
|
|
30
|
+
medium: 'text-md',
|
|
31
|
+
large: 'text-lg',
|
|
32
|
+
giant: 'text-xl',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const inputStyles = tv({
|
|
38
|
+
base: 'w-full outline-none disabled:cursor-not-allowed bg-gray-200 dark:bg-gray-600 disabled:bg-gray-300 disabled:text-gray-200 dark:disabled:bg-gray-800 aria-readonly:text-dark/50 dark:aria-readonly:text-dark/75',
|
|
39
|
+
variants: {
|
|
40
|
+
shape: {
|
|
41
|
+
rectangle: 'rounded-none',
|
|
42
|
+
'semi-round': '',
|
|
43
|
+
round: 'rounded-full',
|
|
44
|
+
},
|
|
45
|
+
padding: {
|
|
46
|
+
base: 'px-3 py-2',
|
|
47
|
+
round: 'px-4 py-2',
|
|
48
|
+
},
|
|
49
|
+
roundedSize: {
|
|
50
|
+
tiny: 'rounded-xl',
|
|
51
|
+
small: 'rounded-xl',
|
|
52
|
+
medium: 'rounded-2xl',
|
|
53
|
+
large: 'rounded-2xl',
|
|
54
|
+
giant: 'rounded-2xl',
|
|
55
|
+
},
|
|
56
|
+
textSize: {
|
|
57
|
+
tiny: 'text-xs',
|
|
58
|
+
small: 'text-sm',
|
|
59
|
+
medium: 'text-md',
|
|
60
|
+
large: 'text-lg',
|
|
61
|
+
giant: 'text-xl',
|
|
62
|
+
},
|
|
63
|
+
invalid: {
|
|
64
|
+
true: 'border border-danger/80',
|
|
65
|
+
false: '',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
const id = generateId();
|
|
71
|
+
const inputId = `input-${id}`;
|
|
72
|
+
const labelId = `label-${id}`;
|
|
12
73
|
</script>
|
|
13
74
|
|
|
14
|
-
<
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
75
|
+
<div class="flex flex-col gap-1">
|
|
76
|
+
{#if label}
|
|
77
|
+
<label id={labelId} for={inputId} class={labelStyles({ size })}>{label}</label>
|
|
78
|
+
{/if}
|
|
79
|
+
|
|
80
|
+
<div class="relative">
|
|
81
|
+
<input
|
|
82
|
+
id={label && inputId}
|
|
83
|
+
aria-labelledby={label && labelId}
|
|
84
|
+
{required}
|
|
85
|
+
aria-required={required}
|
|
86
|
+
{disabled}
|
|
87
|
+
aria-disabled={disabled}
|
|
88
|
+
readonly={readOnly}
|
|
89
|
+
aria-readonly={readOnly}
|
|
90
|
+
class={cleanClass(
|
|
91
|
+
inputStyles({
|
|
92
|
+
shape,
|
|
93
|
+
textSize: size,
|
|
94
|
+
padding: shape === 'round' ? 'round' : 'base',
|
|
95
|
+
roundedSize: shape === 'semi-round' ? size : undefined,
|
|
96
|
+
invalid,
|
|
97
|
+
}),
|
|
98
|
+
trailingIcon && '!pr-10',
|
|
99
|
+
className,
|
|
100
|
+
)}
|
|
101
|
+
bind:value
|
|
102
|
+
{...restProps}
|
|
103
|
+
/>
|
|
104
|
+
{#if trailingIcon}
|
|
105
|
+
<div tabindex="-1" class={cleanClass('absolute inset-y-0 end-0 flex items-center')}>
|
|
106
|
+
{@render trailingIcon()}
|
|
107
|
+
</div>
|
|
108
|
+
{/if}
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
|
|
112
|
+
<style>
|
|
113
|
+
input::-ms-reveal {
|
|
114
|
+
display: none;
|
|
115
|
+
}
|
|
116
|
+
</style>
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
declare const Input: import("svelte").Component<WithElementRef<HTMLInputAttributes>, {}, "value" | "ref">;
|
|
1
|
+
import type { InputProps } from '../../types.js';
|
|
2
|
+
declare const Input: import("svelte").Component<InputProps, {}, "value">;
|
|
4
3
|
export default Input;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { IconButton, Input, type PasswordInputProps } from '../../index.js';
|
|
3
|
+
import { mdiEyeOffOutline, mdiEyeOutline } from '@mdi/js';
|
|
4
|
+
|
|
5
|
+
let {
|
|
6
|
+
value = $bindable<string>(),
|
|
7
|
+
showLabel = 'Show password',
|
|
8
|
+
hideLabel = 'Hide password',
|
|
9
|
+
isVisible = $bindable<boolean>(false),
|
|
10
|
+
color = 'secondary',
|
|
11
|
+
...props
|
|
12
|
+
}: PasswordInputProps = $props();
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<Input bind:value type={isVisible ? 'text' : 'password'} {color} {...props}>
|
|
16
|
+
{#snippet trailingIcon()}
|
|
17
|
+
{#if value?.length > 0}
|
|
18
|
+
<IconButton
|
|
19
|
+
variant="ghost"
|
|
20
|
+
shape="round"
|
|
21
|
+
color="secondary"
|
|
22
|
+
class="m-1"
|
|
23
|
+
icon={isVisible ? mdiEyeOffOutline : mdiEyeOutline}
|
|
24
|
+
onclick={() => (isVisible = !isVisible)}
|
|
25
|
+
title={isVisible ? hideLabel : showLabel}
|
|
26
|
+
></IconButton>
|
|
27
|
+
{/if}
|
|
28
|
+
{/snippet}
|
|
29
|
+
</Input>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { Color, Size } from '../../types.js';
|
|
3
|
+
import { cleanClass } from '../../utils.js';
|
|
4
|
+
import { tv } from 'tailwind-variants';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
size?: Size;
|
|
8
|
+
color?: Color;
|
|
9
|
+
class?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { size = 'medium', color = 'primary', class: className }: Props = $props();
|
|
13
|
+
|
|
14
|
+
const styles = tv({
|
|
15
|
+
base: 'animate-spin fill-primary text-gray-400 dark:text-gray-600',
|
|
16
|
+
variants: {
|
|
17
|
+
size: {
|
|
18
|
+
tiny: 'h-3',
|
|
19
|
+
small: 'h-4',
|
|
20
|
+
medium: 'h-5',
|
|
21
|
+
large: 'h-6',
|
|
22
|
+
giant: 'h-12',
|
|
23
|
+
},
|
|
24
|
+
color: {
|
|
25
|
+
primary: 'fill-primary',
|
|
26
|
+
secondary: 'fill-dark',
|
|
27
|
+
success: 'fill-success',
|
|
28
|
+
danger: 'fill-danger',
|
|
29
|
+
warning: 'fill-warning',
|
|
30
|
+
info: 'fill-info',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
</script>
|
|
35
|
+
|
|
36
|
+
<div>
|
|
37
|
+
<svg
|
|
38
|
+
role="status"
|
|
39
|
+
class={cleanClass(styles({ color, size }), className)}
|
|
40
|
+
viewBox="0 0 100 101"
|
|
41
|
+
fill="none"
|
|
42
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
43
|
+
data-testid="loading-spinner"
|
|
44
|
+
>
|
|
45
|
+
<path
|
|
46
|
+
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
|
47
|
+
fill="currentColor"
|
|
48
|
+
/>
|
|
49
|
+
<path
|
|
50
|
+
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
|
51
|
+
fill="currentFill"
|
|
52
|
+
/>
|
|
53
|
+
</svg>
|
|
54
|
+
</div>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
declare const HStack: import("svelte").Component<{
|
|
2
2
|
class?: string;
|
|
3
3
|
children: import("svelte").Snippet;
|
|
4
|
-
align?: "start" | "center" | "end";
|
|
5
4
|
gap?: import("../../types.js").Gap;
|
|
6
5
|
wrap?: boolean;
|
|
6
|
+
fullWidth?: boolean;
|
|
7
|
+
fullHeight?: boolean;
|
|
7
8
|
}, {}, "">;
|
|
8
9
|
export default HStack;
|
|
@@ -24,14 +24,6 @@
|
|
|
24
24
|
center: 'items-center',
|
|
25
25
|
end: 'items-end',
|
|
26
26
|
},
|
|
27
|
-
fullWidth: {
|
|
28
|
-
true: 'w-full',
|
|
29
|
-
false: '',
|
|
30
|
-
},
|
|
31
|
-
fullHeight: {
|
|
32
|
-
true: 'h-full',
|
|
33
|
-
false: '',
|
|
34
|
-
},
|
|
35
27
|
gap: {
|
|
36
28
|
0: 'gap-0',
|
|
37
29
|
1: 'gap-1',
|
|
@@ -58,8 +50,6 @@
|
|
|
58
50
|
direction,
|
|
59
51
|
gap,
|
|
60
52
|
wrap,
|
|
61
|
-
fullWidth: direction === 'row',
|
|
62
|
-
fullHeight: direction === 'column',
|
|
63
53
|
}),
|
|
64
54
|
className,
|
|
65
55
|
)}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
declare const VStack: import("svelte").Component<{
|
|
2
2
|
class?: string;
|
|
3
3
|
children: import("svelte").Snippet;
|
|
4
|
-
align?: "start" | "center" | "end";
|
|
5
4
|
gap?: import("../../types.js").Gap;
|
|
6
5
|
wrap?: boolean;
|
|
6
|
+
fullWidth?: boolean;
|
|
7
|
+
fullHeight?: boolean;
|
|
7
8
|
}, {}, "">;
|
|
8
9
|
export default VStack;
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
|
|
7
7
|
type Props = {
|
|
8
8
|
color?: Color;
|
|
9
|
+
class?: string;
|
|
9
10
|
size?: Size;
|
|
10
11
|
children: Snippet;
|
|
11
12
|
variant?: 'italic';
|
|
12
13
|
fontWeight?: 'light' | 'normal' | 'semi-bold' | 'bold';
|
|
13
|
-
class?: string;
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
const { color, size, fontWeight = 'normal', children, class: className }: Props = $props();
|
|
@@ -2,10 +2,10 @@ import type { Color, Size } from '../../types.js';
|
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
declare const Text: import("svelte").Component<{
|
|
4
4
|
color?: Color;
|
|
5
|
+
class?: string;
|
|
5
6
|
size?: Size;
|
|
6
7
|
children: Snippet;
|
|
7
8
|
variant?: "italic";
|
|
8
9
|
fontWeight?: "light" | "normal" | "semi-bold" | "bold";
|
|
9
|
-
class?: string;
|
|
10
10
|
}, {}, "">;
|
|
11
11
|
export default Text;
|
package/dist/constants.d.ts
CHANGED
package/dist/constants.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
export var
|
|
2
|
-
(function (
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
export var ChildKey;
|
|
2
|
+
(function (ChildKey) {
|
|
3
|
+
ChildKey["Field"] = "field";
|
|
4
|
+
ChildKey["HelperText"] = "helped-text";
|
|
5
|
+
ChildKey["Card"] = "card";
|
|
6
|
+
ChildKey["CardHeader"] = "card-header";
|
|
7
|
+
ChildKey["CardBody"] = "card-body";
|
|
8
|
+
ChildKey["CardFooter"] = "card-footer";
|
|
9
|
+
})(ChildKey || (ChildKey = {}));
|
package/dist/index.d.ts
CHANGED
|
@@ -8,16 +8,20 @@ export { default as CardHeader } from './components/Card/CardHeader.svelte';
|
|
|
8
8
|
export { default as CardTitle } from './components/Card/CardTitle.svelte';
|
|
9
9
|
export { default as CloseButton } from './components/CloseButton/CloseButton.svelte';
|
|
10
10
|
export { default as Checkbox } from './components/Form/Checkbox.svelte';
|
|
11
|
+
export { default as Field } from './components/Form/Field.svelte';
|
|
12
|
+
export { default as HelperText } from './components/Form/HelperText.svelte';
|
|
11
13
|
export { default as Input } from './components/Form/Input.svelte';
|
|
12
14
|
export { default as Label } from './components/Form/Label.svelte';
|
|
15
|
+
export { default as PasswordInput } from './components/Form/PasswordInput.svelte';
|
|
13
16
|
export { default as Heading } from './components/Heading/Heading.svelte';
|
|
14
17
|
export { default as Icon } from './components/Icon/Icon.svelte';
|
|
15
18
|
export { default as IconButton } from './components/IconButton/IconButton.svelte';
|
|
16
19
|
export { default as Link } from './components/Link/Link.svelte';
|
|
17
|
-
export { default as
|
|
20
|
+
export { default as LoadingSpinner } from './components/LoadingSpinner/LoadingSpinner.svelte';
|
|
18
21
|
export { default as Logo } from './components/Logo/Logo.svelte';
|
|
19
22
|
export { default as HStack } from './components/Stack/HStack.svelte';
|
|
20
23
|
export { default as Stack } from './components/Stack/Stack.svelte';
|
|
21
24
|
export { default as VStack } from './components/Stack/VStack.svelte';
|
|
25
|
+
export { default as SupporterBadge } from './components/SupporterBadge/SupporterBadge.svelte';
|
|
22
26
|
export { default as Text } from './components/Text/Text.svelte';
|
|
23
27
|
export * from './types.js';
|
package/dist/index.js
CHANGED
|
@@ -8,16 +8,20 @@ export { default as CardHeader } from './components/Card/CardHeader.svelte';
|
|
|
8
8
|
export { default as CardTitle } from './components/Card/CardTitle.svelte';
|
|
9
9
|
export { default as CloseButton } from './components/CloseButton/CloseButton.svelte';
|
|
10
10
|
export { default as Checkbox } from './components/Form/Checkbox.svelte';
|
|
11
|
+
export { default as Field } from './components/Form/Field.svelte';
|
|
12
|
+
export { default as HelperText } from './components/Form/HelperText.svelte';
|
|
11
13
|
export { default as Input } from './components/Form/Input.svelte';
|
|
12
14
|
export { default as Label } from './components/Form/Label.svelte';
|
|
15
|
+
export { default as PasswordInput } from './components/Form/PasswordInput.svelte';
|
|
13
16
|
export { default as Heading } from './components/Heading/Heading.svelte';
|
|
14
17
|
export { default as Icon } from './components/Icon/Icon.svelte';
|
|
15
18
|
export { default as IconButton } from './components/IconButton/IconButton.svelte';
|
|
16
19
|
export { default as Link } from './components/Link/Link.svelte';
|
|
17
|
-
export { default as
|
|
20
|
+
export { default as LoadingSpinner } from './components/LoadingSpinner/LoadingSpinner.svelte';
|
|
18
21
|
export { default as Logo } from './components/Logo/Logo.svelte';
|
|
19
22
|
export { default as HStack } from './components/Stack/HStack.svelte';
|
|
20
23
|
export { default as Stack } from './components/Stack/Stack.svelte';
|
|
21
24
|
export { default as VStack } from './components/Stack/VStack.svelte';
|
|
25
|
+
export { default as SupporterBadge } from './components/SupporterBadge/SupporterBadge.svelte';
|
|
22
26
|
export { default as Text } from './components/Text/Text.svelte';
|
|
23
27
|
export * from './types.js';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { ButtonProps } from '../types.js';
|
|
2
|
+
import type { ButtonProps, Size } from '../types.js';
|
|
3
3
|
import { cleanClass } from '../utils.js';
|
|
4
|
+
import { LoadingSpinner } from '../index.js';
|
|
4
5
|
import { Button as ButtonPrimitive } from 'bits-ui';
|
|
5
6
|
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
|
6
7
|
import { twMerge } from 'tailwind-merge';
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
color = 'primary',
|
|
19
20
|
shape = 'semi-round',
|
|
20
21
|
size = 'medium',
|
|
22
|
+
loading = false,
|
|
21
23
|
fullWidth = false,
|
|
22
24
|
icon = false,
|
|
23
25
|
class: className = '',
|
|
@@ -25,6 +27,8 @@
|
|
|
25
27
|
...restProps
|
|
26
28
|
}: InternalButtonProps = $props();
|
|
27
29
|
|
|
30
|
+
const disabled = $derived((restProps as HTMLButtonAttributes).disabled || loading);
|
|
31
|
+
|
|
28
32
|
const buttonVariants = tv({
|
|
29
33
|
base: 'ring-offset-background focus-visible:ring-ring flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
|
30
34
|
variants: {
|
|
@@ -58,11 +62,11 @@
|
|
|
58
62
|
giant: 'h-12 w-12 text-lg',
|
|
59
63
|
},
|
|
60
64
|
roundedSize: {
|
|
61
|
-
tiny: 'rounded-
|
|
62
|
-
small: 'rounded-
|
|
63
|
-
medium: 'rounded-
|
|
64
|
-
large: 'rounded-
|
|
65
|
-
giant: 'rounded-
|
|
65
|
+
tiny: 'rounded-lg',
|
|
66
|
+
small: 'rounded-lg',
|
|
67
|
+
medium: 'rounded-xl',
|
|
68
|
+
large: 'rounded-xl',
|
|
69
|
+
giant: 'rounded-2xl',
|
|
66
70
|
},
|
|
67
71
|
filledColor: {
|
|
68
72
|
primary: 'bg-primary text-light hover:bg-primary/80',
|
|
@@ -99,6 +103,14 @@
|
|
|
99
103
|
},
|
|
100
104
|
});
|
|
101
105
|
|
|
106
|
+
const spinnerSizes: Record<Size, Size> = {
|
|
107
|
+
tiny: 'tiny',
|
|
108
|
+
small: 'tiny',
|
|
109
|
+
medium: 'small',
|
|
110
|
+
large: 'medium',
|
|
111
|
+
giant: 'large',
|
|
112
|
+
};
|
|
113
|
+
|
|
102
114
|
const classList = $derived(
|
|
103
115
|
cleanClass(
|
|
104
116
|
twMerge(
|
|
@@ -129,7 +141,16 @@
|
|
|
129
141
|
class={classList}
|
|
130
142
|
type={type as HTMLButtonAttributes['type']}
|
|
131
143
|
{...restProps as HTMLButtonAttributes}
|
|
144
|
+
{disabled}
|
|
145
|
+
aria-disabled={disabled}
|
|
132
146
|
>
|
|
133
|
-
{
|
|
147
|
+
{#if loading}
|
|
148
|
+
<div class="flex items-center justify-center gap-2">
|
|
149
|
+
<LoadingSpinner {color} size={spinnerSizes[size]} />
|
|
150
|
+
{@render children?.()}
|
|
151
|
+
</div>
|
|
152
|
+
{:else}
|
|
153
|
+
{@render children?.()}
|
|
154
|
+
{/if}
|
|
134
155
|
</ButtonPrimitive.Root>
|
|
135
156
|
{/if}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { ChildKey } from '../constants.js';
|
|
3
3
|
import { withPrefix } from '../utils.js';
|
|
4
4
|
import { getContext, type Snippet } from 'svelte';
|
|
5
5
|
|
|
6
|
-
type ContextType = { register: (key:
|
|
6
|
+
type ContextType = { register: (key: ChildKey, snippet: Snippet) => void };
|
|
7
7
|
type Props = {
|
|
8
|
-
for:
|
|
9
|
-
as:
|
|
8
|
+
for: ChildKey;
|
|
9
|
+
as: ChildKey;
|
|
10
10
|
children: Snippet;
|
|
11
11
|
};
|
|
12
12
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ChildKey } from '../constants.js';
|
|
2
2
|
import { type Snippet } from 'svelte';
|
|
3
3
|
declare const Child: import("svelte").Component<{
|
|
4
|
-
for:
|
|
5
|
-
as:
|
|
4
|
+
for: ChildKey;
|
|
5
|
+
as: ChildKey;
|
|
6
6
|
children: Snippet;
|
|
7
7
|
}, {}, "">;
|
|
8
8
|
export default Child;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
|
2
|
+
import type { HTMLAnchorAttributes, HTMLButtonAttributes, HTMLInputAttributes } from 'svelte/elements';
|
|
3
3
|
export type Color = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info';
|
|
4
4
|
export type Size = 'tiny' | 'small' | 'medium' | 'large' | 'giant';
|
|
5
5
|
export type HeadingSize = Size | 'title';
|
|
@@ -37,17 +37,43 @@ export type IconProps = {
|
|
|
37
37
|
export type IconButtonProps = ButtonBaseProps & IconProps;
|
|
38
38
|
export type ButtonProps = ButtonBaseProps & {
|
|
39
39
|
fullWidth?: boolean;
|
|
40
|
+
loading?: boolean;
|
|
40
41
|
};
|
|
41
42
|
type StackBaseProps = {
|
|
42
43
|
class?: string;
|
|
43
44
|
children: Snippet;
|
|
44
|
-
align?: 'start' | 'center' | 'end';
|
|
45
45
|
gap?: Gap;
|
|
46
46
|
wrap?: boolean;
|
|
47
|
+
fullWidth?: boolean;
|
|
48
|
+
fullHeight?: boolean;
|
|
47
49
|
};
|
|
48
50
|
export type StackProps = StackBaseProps & {
|
|
51
|
+
align?: 'start' | 'center' | 'end';
|
|
49
52
|
direction?: 'row' | 'column';
|
|
50
53
|
};
|
|
51
54
|
export type HStackProps = StackBaseProps;
|
|
52
55
|
export type VStackProps = StackBaseProps;
|
|
56
|
+
export type FieldContext = {
|
|
57
|
+
label?: string;
|
|
58
|
+
invalid?: boolean;
|
|
59
|
+
disabled?: boolean;
|
|
60
|
+
required?: boolean;
|
|
61
|
+
readOnly?: boolean;
|
|
62
|
+
};
|
|
63
|
+
type BaseInputProps = {
|
|
64
|
+
class?: string;
|
|
65
|
+
value?: string;
|
|
66
|
+
size?: Size;
|
|
67
|
+
shape?: Shape;
|
|
68
|
+
inputSize?: HTMLInputAttributes['size'];
|
|
69
|
+
} & Omit<HTMLInputAttributes, 'size' | 'type'>;
|
|
70
|
+
export type InputProps = BaseInputProps & {
|
|
71
|
+
type?: HTMLInputAttributes['type'];
|
|
72
|
+
trailingIcon?: Snippet;
|
|
73
|
+
};
|
|
74
|
+
export type PasswordInputProps = BaseInputProps & {
|
|
75
|
+
showLabel?: string;
|
|
76
|
+
hideLabel?: string;
|
|
77
|
+
isVisible?: boolean;
|
|
78
|
+
};
|
|
53
79
|
export {};
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { ContextKey } from './constants.js';
|
|
2
1
|
export declare const cleanClass: (...classNames: unknown[]) => string;
|
|
3
|
-
export declare const withPrefix: (key:
|
|
2
|
+
export declare const withPrefix: (key: string) => string;
|
|
3
|
+
export declare const generateId: () => string;
|
package/dist/utils.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@immich/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"license": "GNU Affero General Public License version 3",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"start": "npm run start:dev",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"svelte-check": "^4.0.0",
|
|
58
58
|
"tailwindcss": "^3.4.9",
|
|
59
59
|
"typescript": "^5.0.0",
|
|
60
|
-
"typescript-eslint": "^8.
|
|
60
|
+
"typescript-eslint": "^8.15.0",
|
|
61
61
|
"vite": "^5.0.11",
|
|
62
62
|
"vitest": "^2.0.4"
|
|
63
63
|
},
|