@immich/ui 0.6.0 → 0.8.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/assets/appstore-badge.svg +46 -0
- package/dist/assets/fdroid-badge.svg +124 -0
- package/dist/assets/playstore-badge.png +0 -0
- package/dist/components/Alert/Alert.svelte +13 -7
- package/dist/components/Alert/Alert.svelte.d.ts +3 -2
- package/dist/components/AppShell/AppShell.svelte +34 -0
- package/dist/components/AppShell/AppShell.svelte.d.ts +6 -0
- package/dist/components/AppShell/AppShellHeader.svelte +15 -0
- package/dist/components/AppShell/AppShellHeader.svelte.d.ts +5 -0
- package/dist/components/AppShell/AppShellSidebar.svelte +23 -0
- package/dist/components/AppShell/AppShellSidebar.svelte.d.ts +7 -0
- package/dist/components/AppShell/PageLayout.svelte +44 -0
- package/dist/components/AppShell/PageLayout.svelte.d.ts +9 -0
- package/dist/components/Avatar/Avatar.svelte +66 -0
- package/dist/components/Avatar/Avatar.svelte.d.ts +7 -0
- package/dist/components/Card/Card.svelte +7 -9
- package/dist/components/Card/CardBody.svelte +1 -1
- package/dist/components/Card/CardFooter.svelte +6 -2
- package/dist/components/Card/CardFooter.svelte.d.ts +1 -0
- package/dist/components/Code/Code.svelte +62 -0
- package/dist/components/Code/Code.svelte.d.ts +9 -0
- package/dist/components/Form/Checkbox.svelte +67 -27
- package/dist/components/Form/Checkbox.svelte.d.ts +2 -2
- package/dist/components/Form/Input.svelte +43 -35
- package/dist/components/Form/Input.svelte.d.ts +2 -9
- package/dist/components/Form/PasswordInput.svelte +31 -0
- package/dist/components/Form/PasswordInput.svelte.d.ts +3 -0
- package/dist/components/FormatBytes/FormatBytes.svelte +16 -0
- package/dist/components/FormatBytes/FormatBytes.svelte.d.ts +6 -0
- package/dist/components/Heading/Heading.svelte +2 -1
- package/dist/components/Heading/Heading.svelte.d.ts +1 -1
- package/dist/components/LoadingSpinner/LoadingSpinner.svelte +54 -0
- package/dist/components/LoadingSpinner/LoadingSpinner.svelte.d.ts +7 -0
- package/dist/components/Logo/Logo.svelte +8 -8
- package/dist/components/Logo/Logo.svelte.d.ts +1 -2
- package/dist/components/MultiSelect/MultiSelect.svelte +15 -0
- package/dist/components/MultiSelect/MultiSelect.svelte.d.ts +3 -0
- package/dist/components/Navbar/NavbarGroup.svelte +12 -0
- package/dist/components/Navbar/NavbarGroup.svelte.d.ts +4 -0
- package/dist/components/Navbar/NavbarItem.svelte +30 -0
- package/dist/components/Navbar/NavbarItem.svelte.d.ts +7 -0
- package/dist/components/Scrollable/Scrollable.svelte +41 -0
- package/dist/components/Scrollable/Scrollable.svelte.d.ts +6 -0
- package/dist/components/Select/Select.svelte +15 -0
- package/dist/components/Select/Select.svelte.d.ts +3 -0
- package/dist/components/Switch/Switch.svelte +99 -0
- package/dist/components/Switch/Switch.svelte.d.ts +10 -0
- package/dist/components/Text/Text.svelte +15 -3
- package/dist/components/Text/Text.svelte.d.ts +1 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +3 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +28 -0
- package/dist/internal/Button.svelte +28 -16
- package/dist/internal/Select.svelte +174 -0
- package/dist/internal/Select.svelte.d.ts +9 -0
- package/dist/services/theme.svelte.d.ts +5 -0
- package/dist/services/theme.svelte.js +13 -0
- package/dist/types.d.ts +45 -2
- package/dist/types.js +5 -1
- package/dist/utilities/byte-units.d.ts +52 -0
- package/dist/utilities/byte-units.js +75 -0
- package/package.json +7 -5
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { cleanClass } from '../../utils.js';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
class?: string;
|
|
7
|
+
children?: Snippet;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const { class: className, children }: Props = $props();
|
|
11
|
+
</script>
|
|
12
|
+
|
|
13
|
+
<div class={cleanClass('immich-scrollbar h-full w-full overflow-auto', className)}>
|
|
14
|
+
{@render children?.()}
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<style>
|
|
18
|
+
/* width */
|
|
19
|
+
.immich-scrollbar::-webkit-scrollbar {
|
|
20
|
+
width: 8px;
|
|
21
|
+
height: 8px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/* Track */
|
|
25
|
+
.immich-scrollbar::-webkit-scrollbar-track {
|
|
26
|
+
background: #f1f1f1;
|
|
27
|
+
border-radius: 16px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Handle */
|
|
31
|
+
.immich-scrollbar::-webkit-scrollbar-thumb {
|
|
32
|
+
background: rgba(85, 86, 87, 0.408);
|
|
33
|
+
border-radius: 16px;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Handle on hover */
|
|
37
|
+
.immich-scrollbar::-webkit-scrollbar-thumb:hover {
|
|
38
|
+
background: #4250afad;
|
|
39
|
+
border-radius: 16px;
|
|
40
|
+
}
|
|
41
|
+
</style>
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import InternalSelect from '../../internal/Select.svelte';
|
|
3
|
+
import type { SelectItem, SelectProps } from '../../types.js';
|
|
4
|
+
|
|
5
|
+
type T = SelectItem;
|
|
6
|
+
|
|
7
|
+
let { value = $bindable(), onChange, ...restProps }: SelectProps<T> = $props();
|
|
8
|
+
|
|
9
|
+
const handleChange = (items: T[]) => {
|
|
10
|
+
value = items[0] as T;
|
|
11
|
+
onChange?.(value);
|
|
12
|
+
};
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<InternalSelect onChange={handleChange} {...restProps} />
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getFieldContext } from '../../common/context.svelte.js';
|
|
3
|
+
import type { Color } from '../../types.js';
|
|
4
|
+
import { cleanClass } from '../../utils.js';
|
|
5
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
6
|
+
import { tv } from 'tailwind-variants';
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
checked?: boolean;
|
|
10
|
+
color?: Color;
|
|
11
|
+
disabled?: boolean;
|
|
12
|
+
class?: string;
|
|
13
|
+
onToggle?: ((checked: boolean) => void) | undefined;
|
|
14
|
+
} & HTMLInputAttributes;
|
|
15
|
+
|
|
16
|
+
let {
|
|
17
|
+
checked = $bindable(false),
|
|
18
|
+
class: className,
|
|
19
|
+
color = 'primary',
|
|
20
|
+
onToggle = undefined,
|
|
21
|
+
...restProps
|
|
22
|
+
}: Props = $props();
|
|
23
|
+
|
|
24
|
+
const {
|
|
25
|
+
label,
|
|
26
|
+
readOnly = false,
|
|
27
|
+
required = false,
|
|
28
|
+
disabled = false,
|
|
29
|
+
} = $derived(getFieldContext());
|
|
30
|
+
|
|
31
|
+
const enabled = $derived(checked && !disabled);
|
|
32
|
+
|
|
33
|
+
const handleToggle = (event: Event) => onToggle?.((event.target as HTMLInputElement).checked);
|
|
34
|
+
|
|
35
|
+
const wrapper = tv({
|
|
36
|
+
base: 'relative flex flex-col justify-center',
|
|
37
|
+
variants: {
|
|
38
|
+
disabled: {
|
|
39
|
+
true: 'cursor-not-allowed',
|
|
40
|
+
false: 'cursor-pointer',
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const bar = tv({
|
|
46
|
+
base: 'w-12 h-3 my-2 rounded-full border border-transparent',
|
|
47
|
+
variants: {
|
|
48
|
+
fillColor: {
|
|
49
|
+
default: 'bg-gray-400',
|
|
50
|
+
primary: 'bg-primary/50',
|
|
51
|
+
secondary: 'bg-dark/50',
|
|
52
|
+
success: 'bg-success/50',
|
|
53
|
+
danger: 'bg-danger/50',
|
|
54
|
+
warning: 'bg-warning/50',
|
|
55
|
+
info: 'bg-info/50',
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const dot = tv({
|
|
61
|
+
base: 'absolute transition-colors h-6 w-6 rounded-full transition-transform duration-[400ms]',
|
|
62
|
+
variants: {
|
|
63
|
+
checked: {
|
|
64
|
+
true: 'translate-x-6',
|
|
65
|
+
false: '',
|
|
66
|
+
},
|
|
67
|
+
fillColor: {
|
|
68
|
+
default: 'bg-gray-600',
|
|
69
|
+
primary: 'bg-primary',
|
|
70
|
+
secondary: 'bg-dark',
|
|
71
|
+
success: 'bg-success',
|
|
72
|
+
danger: 'bg-danger',
|
|
73
|
+
warning: 'bg-warning',
|
|
74
|
+
info: 'bg-info',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
</script>
|
|
79
|
+
|
|
80
|
+
<label class={cleanClass(className)}>
|
|
81
|
+
{label}
|
|
82
|
+
<span class={wrapper({ disabled })}>
|
|
83
|
+
<input
|
|
84
|
+
class="hidden"
|
|
85
|
+
type="checkbox"
|
|
86
|
+
bind:checked
|
|
87
|
+
onclick={handleToggle}
|
|
88
|
+
{required}
|
|
89
|
+
aria-required={required}
|
|
90
|
+
{disabled}
|
|
91
|
+
aria-disabled={disabled}
|
|
92
|
+
readonly={readOnly}
|
|
93
|
+
aria-readonly={readOnly}
|
|
94
|
+
{...restProps}
|
|
95
|
+
/>
|
|
96
|
+
<span class={bar({ fillColor: enabled ? color : 'default' })}> </span>
|
|
97
|
+
<span class={dot({ checked: enabled, fillColor: enabled ? color : 'default' })}></span>
|
|
98
|
+
</span>
|
|
99
|
+
</label>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Color } from '../../types.js';
|
|
2
|
+
import type { HTMLInputAttributes } from 'svelte/elements';
|
|
3
|
+
declare const Switch: import("svelte").Component<{
|
|
4
|
+
checked?: boolean;
|
|
5
|
+
color?: Color;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
class?: string;
|
|
8
|
+
onToggle?: ((checked: boolean) => void) | undefined;
|
|
9
|
+
} & HTMLInputAttributes, {}, "checked">;
|
|
10
|
+
export default Switch;
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { tv } from 'tailwind-variants';
|
|
6
6
|
|
|
7
7
|
type Props = {
|
|
8
|
-
color?: Color;
|
|
8
|
+
color?: Color | 'muted';
|
|
9
9
|
class?: string;
|
|
10
10
|
size?: Size;
|
|
11
11
|
children: Snippet;
|
|
@@ -13,11 +13,19 @@
|
|
|
13
13
|
fontWeight?: 'light' | 'normal' | 'semi-bold' | 'bold';
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
-
const {
|
|
16
|
+
const {
|
|
17
|
+
color,
|
|
18
|
+
size,
|
|
19
|
+
variant,
|
|
20
|
+
fontWeight = 'normal',
|
|
21
|
+
children,
|
|
22
|
+
class: className,
|
|
23
|
+
}: Props = $props();
|
|
17
24
|
|
|
18
25
|
const styles = tv({
|
|
19
26
|
variants: {
|
|
20
27
|
color: {
|
|
28
|
+
muted: 'text-gray-600 dark:text-gray-400',
|
|
21
29
|
primary: 'text-primary',
|
|
22
30
|
secondary: 'text-dark',
|
|
23
31
|
success: 'text-success',
|
|
@@ -26,6 +34,10 @@
|
|
|
26
34
|
info: 'text-info',
|
|
27
35
|
},
|
|
28
36
|
|
|
37
|
+
variant: {
|
|
38
|
+
italic: 'italic',
|
|
39
|
+
},
|
|
40
|
+
|
|
29
41
|
size: {
|
|
30
42
|
tiny: 'text-xs',
|
|
31
43
|
small: 'text-sm',
|
|
@@ -44,6 +56,6 @@
|
|
|
44
56
|
});
|
|
45
57
|
</script>
|
|
46
58
|
|
|
47
|
-
<p class={cleanClass(styles({ color, size, fontWeight }), className)}>
|
|
59
|
+
<p class={cleanClass(styles({ color, size, fontWeight, variant }), className)}>
|
|
48
60
|
{@render children()}
|
|
49
61
|
</p>
|
package/dist/constants.d.ts
CHANGED
package/dist/constants.js
CHANGED
|
@@ -2,6 +2,9 @@ export var ChildKey;
|
|
|
2
2
|
(function (ChildKey) {
|
|
3
3
|
ChildKey["Field"] = "field";
|
|
4
4
|
ChildKey["HelperText"] = "helped-text";
|
|
5
|
+
ChildKey["AppShell"] = "app-shell";
|
|
6
|
+
ChildKey["AppShellHeader"] = "app-shell-header";
|
|
7
|
+
ChildKey["AppShellSidebar"] = "app-shell-sidebar";
|
|
5
8
|
ChildKey["Card"] = "card";
|
|
6
9
|
ChildKey["CardHeader"] = "card-header";
|
|
7
10
|
ChildKey["CardBody"] = "card-body";
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
+
export { default as appStoreBadge } from './assets/appstore-badge.svg';
|
|
2
|
+
export { default as fdroidBadge } from './assets/fdroid-badge.svg';
|
|
3
|
+
export { default as immichLogoInlineDark } from './assets/immich-logo-inline-dark.svg';
|
|
4
|
+
export { default as immichLogoInlineLight } from './assets/immich-logo-inline-light.svg';
|
|
5
|
+
export { default as immichLogoStackedDark } from './assets/immich-logo-stacked-dark.svg';
|
|
6
|
+
export { default as immichLogoStackedLight } from './assets/immich-logo-stacked-light.svg';
|
|
7
|
+
export { default as immichLogoJson } from './assets/immich-logo.json';
|
|
8
|
+
export { default as immichLogo } from './assets/immich-logo.svg';
|
|
9
|
+
export { default as playStoreBadge } from './assets/playstore-badge.png';
|
|
1
10
|
export { default as Alert } from './components/Alert/Alert.svelte';
|
|
11
|
+
export { default as AppShell } from './components/AppShell/AppShell.svelte';
|
|
12
|
+
export { default as AppShellHeader } from './components/AppShell/AppShellHeader.svelte';
|
|
13
|
+
export { default as AppShellSidebar } from './components/AppShell/AppShellSidebar.svelte';
|
|
14
|
+
export { default as Avatar } from './components/Avatar/Avatar.svelte';
|
|
2
15
|
export { default as Button } from './components/Button/Button.svelte';
|
|
3
16
|
export { default as Card } from './components/Card/Card.svelte';
|
|
4
17
|
export { default as CardBody } from './components/Card/CardBody.svelte';
|
|
@@ -7,19 +20,31 @@ export { default as CardFooter } from './components/Card/CardFooter.svelte';
|
|
|
7
20
|
export { default as CardHeader } from './components/Card/CardHeader.svelte';
|
|
8
21
|
export { default as CardTitle } from './components/Card/CardTitle.svelte';
|
|
9
22
|
export { default as CloseButton } from './components/CloseButton/CloseButton.svelte';
|
|
23
|
+
export { default as Code } from './components/Code/Code.svelte';
|
|
10
24
|
export { default as Checkbox } from './components/Form/Checkbox.svelte';
|
|
11
25
|
export { default as Field } from './components/Form/Field.svelte';
|
|
12
26
|
export { default as HelperText } from './components/Form/HelperText.svelte';
|
|
13
27
|
export { default as Input } from './components/Form/Input.svelte';
|
|
14
28
|
export { default as Label } from './components/Form/Label.svelte';
|
|
29
|
+
export { default as PasswordInput } from './components/Form/PasswordInput.svelte';
|
|
30
|
+
export { default as FormatBytes } from './components/FormatBytes/FormatBytes.svelte';
|
|
15
31
|
export { default as Heading } from './components/Heading/Heading.svelte';
|
|
16
32
|
export { default as Icon } from './components/Icon/Icon.svelte';
|
|
17
33
|
export { default as IconButton } from './components/IconButton/IconButton.svelte';
|
|
18
34
|
export { default as Link } from './components/Link/Link.svelte';
|
|
35
|
+
export { default as LoadingSpinner } from './components/LoadingSpinner/LoadingSpinner.svelte';
|
|
19
36
|
export { default as Logo } from './components/Logo/Logo.svelte';
|
|
37
|
+
export { default as MultiSelect } from './components/MultiSelect/MultiSelect.svelte';
|
|
38
|
+
export { default as NavbarGroup } from './components/Navbar/NavbarGroup.svelte';
|
|
39
|
+
export { default as NavbarItem } from './components/Navbar/NavbarItem.svelte';
|
|
40
|
+
export { default as Scrollable } from './components/Scrollable/Scrollable.svelte';
|
|
41
|
+
export { default as Select } from './components/Select/Select.svelte';
|
|
20
42
|
export { default as HStack } from './components/Stack/HStack.svelte';
|
|
21
43
|
export { default as Stack } from './components/Stack/Stack.svelte';
|
|
22
44
|
export { default as VStack } from './components/Stack/VStack.svelte';
|
|
23
45
|
export { default as SupporterBadge } from './components/SupporterBadge/SupporterBadge.svelte';
|
|
46
|
+
export { default as Switch } from './components/Switch/Switch.svelte';
|
|
24
47
|
export { default as Text } from './components/Text/Text.svelte';
|
|
48
|
+
export * from './services/theme.svelte.js';
|
|
25
49
|
export * from './types.js';
|
|
50
|
+
export * from './utilities/byte-units.js';
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,19 @@
|
|
|
1
|
+
// files
|
|
2
|
+
export { default as appStoreBadge } from './assets/appstore-badge.svg';
|
|
3
|
+
export { default as fdroidBadge } from './assets/fdroid-badge.svg';
|
|
4
|
+
export { default as immichLogoInlineDark } from './assets/immich-logo-inline-dark.svg';
|
|
5
|
+
export { default as immichLogoInlineLight } from './assets/immich-logo-inline-light.svg';
|
|
6
|
+
export { default as immichLogoStackedDark } from './assets/immich-logo-stacked-dark.svg';
|
|
7
|
+
export { default as immichLogoStackedLight } from './assets/immich-logo-stacked-light.svg';
|
|
8
|
+
export { default as immichLogoJson } from './assets/immich-logo.json';
|
|
9
|
+
export { default as immichLogo } from './assets/immich-logo.svg';
|
|
10
|
+
export { default as playStoreBadge } from './assets/playstore-badge.png';
|
|
11
|
+
// components
|
|
1
12
|
export { default as Alert } from './components/Alert/Alert.svelte';
|
|
13
|
+
export { default as AppShell } from './components/AppShell/AppShell.svelte';
|
|
14
|
+
export { default as AppShellHeader } from './components/AppShell/AppShellHeader.svelte';
|
|
15
|
+
export { default as AppShellSidebar } from './components/AppShell/AppShellSidebar.svelte';
|
|
16
|
+
export { default as Avatar } from './components/Avatar/Avatar.svelte';
|
|
2
17
|
export { default as Button } from './components/Button/Button.svelte';
|
|
3
18
|
export { default as Card } from './components/Card/Card.svelte';
|
|
4
19
|
export { default as CardBody } from './components/Card/CardBody.svelte';
|
|
@@ -7,19 +22,32 @@ export { default as CardFooter } from './components/Card/CardFooter.svelte';
|
|
|
7
22
|
export { default as CardHeader } from './components/Card/CardHeader.svelte';
|
|
8
23
|
export { default as CardTitle } from './components/Card/CardTitle.svelte';
|
|
9
24
|
export { default as CloseButton } from './components/CloseButton/CloseButton.svelte';
|
|
25
|
+
export { default as Code } from './components/Code/Code.svelte';
|
|
10
26
|
export { default as Checkbox } from './components/Form/Checkbox.svelte';
|
|
11
27
|
export { default as Field } from './components/Form/Field.svelte';
|
|
12
28
|
export { default as HelperText } from './components/Form/HelperText.svelte';
|
|
13
29
|
export { default as Input } from './components/Form/Input.svelte';
|
|
14
30
|
export { default as Label } from './components/Form/Label.svelte';
|
|
31
|
+
export { default as PasswordInput } from './components/Form/PasswordInput.svelte';
|
|
32
|
+
export { default as FormatBytes } from './components/FormatBytes/FormatBytes.svelte';
|
|
15
33
|
export { default as Heading } from './components/Heading/Heading.svelte';
|
|
16
34
|
export { default as Icon } from './components/Icon/Icon.svelte';
|
|
17
35
|
export { default as IconButton } from './components/IconButton/IconButton.svelte';
|
|
18
36
|
export { default as Link } from './components/Link/Link.svelte';
|
|
37
|
+
export { default as LoadingSpinner } from './components/LoadingSpinner/LoadingSpinner.svelte';
|
|
19
38
|
export { default as Logo } from './components/Logo/Logo.svelte';
|
|
39
|
+
export { default as MultiSelect } from './components/MultiSelect/MultiSelect.svelte';
|
|
40
|
+
export { default as NavbarGroup } from './components/Navbar/NavbarGroup.svelte';
|
|
41
|
+
export { default as NavbarItem } from './components/Navbar/NavbarItem.svelte';
|
|
42
|
+
export { default as Scrollable } from './components/Scrollable/Scrollable.svelte';
|
|
43
|
+
export { default as Select } from './components/Select/Select.svelte';
|
|
20
44
|
export { default as HStack } from './components/Stack/HStack.svelte';
|
|
21
45
|
export { default as Stack } from './components/Stack/Stack.svelte';
|
|
22
46
|
export { default as VStack } from './components/Stack/VStack.svelte';
|
|
23
47
|
export { default as SupporterBadge } from './components/SupporterBadge/SupporterBadge.svelte';
|
|
48
|
+
export { default as Switch } from './components/Switch/Switch.svelte';
|
|
24
49
|
export { default as Text } from './components/Text/Text.svelte';
|
|
50
|
+
// helpers
|
|
51
|
+
export * from './services/theme.svelte.js';
|
|
25
52
|
export * from './types.js';
|
|
53
|
+
export * from './utilities/byte-units.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import
|
|
2
|
+
import LoadingSpinner from '../components/LoadingSpinner/LoadingSpinner.svelte';
|
|
3
|
+
import type { ButtonProps, Size } from '../types.js';
|
|
3
4
|
import { cleanClass } from '../utils.js';
|
|
4
5
|
import { Button as ButtonPrimitive } from 'bits-ui';
|
|
5
6
|
import type { HTMLAnchorAttributes, HTMLButtonAttributes } from 'svelte/elements';
|
|
@@ -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',
|
|
@@ -80,14 +84,6 @@
|
|
|
80
84
|
warning: 'bg-warning/10 text-warning border border-warning hover:bg-warning/20',
|
|
81
85
|
info: 'bg-info/10 text-info border border-info hover:bg-info/20',
|
|
82
86
|
},
|
|
83
|
-
heroColor: {
|
|
84
|
-
primary: 'bg-gradient-to-tr from-primary to-primary/60 text-light hover:bg-primary',
|
|
85
|
-
secondary: 'bg-gradient-to-tr from-dark to-dark/60 text-light hover:bg-dark',
|
|
86
|
-
success: 'bg-gradient-to-tr from-success to-success/60 text-light hover:bg-success',
|
|
87
|
-
danger: 'bg-gradient-to-tr from-danger to-danger/60 text-light hover:bg-danger',
|
|
88
|
-
warning: 'bg-gradient-to-tr from-warning to-warning/60 text-light hover:bg-warning',
|
|
89
|
-
info: 'bg-gradient-to-tr from-info to-info/60 text-light hover:bg-info',
|
|
90
|
-
},
|
|
91
87
|
ghostColor: {
|
|
92
88
|
primary: 'text-primary hover:bg-primary/10',
|
|
93
89
|
secondary: 'text-dark hover:bg-dark/10',
|
|
@@ -99,6 +95,14 @@
|
|
|
99
95
|
},
|
|
100
96
|
});
|
|
101
97
|
|
|
98
|
+
const spinnerSizes: Record<Size, Size> = {
|
|
99
|
+
tiny: 'tiny',
|
|
100
|
+
small: 'tiny',
|
|
101
|
+
medium: 'small',
|
|
102
|
+
large: 'medium',
|
|
103
|
+
giant: 'large',
|
|
104
|
+
};
|
|
105
|
+
|
|
102
106
|
const classList = $derived(
|
|
103
107
|
cleanClass(
|
|
104
108
|
twMerge(
|
|
@@ -111,7 +115,6 @@
|
|
|
111
115
|
roundedSize: shape === 'semi-round' ? size : undefined,
|
|
112
116
|
filledColor: variant === 'filled' ? color : undefined,
|
|
113
117
|
outlineColor: variant === 'outline' ? color : undefined,
|
|
114
|
-
heroColor: variant === 'hero' ? color : undefined,
|
|
115
118
|
ghostColor: variant === 'ghost' ? color : undefined,
|
|
116
119
|
}),
|
|
117
120
|
className,
|
|
@@ -129,7 +132,16 @@
|
|
|
129
132
|
class={classList}
|
|
130
133
|
type={type as HTMLButtonAttributes['type']}
|
|
131
134
|
{...restProps as HTMLButtonAttributes}
|
|
135
|
+
{disabled}
|
|
136
|
+
aria-disabled={disabled}
|
|
132
137
|
>
|
|
133
|
-
{
|
|
138
|
+
{#if loading}
|
|
139
|
+
<div class="flex items-center justify-center gap-2">
|
|
140
|
+
<LoadingSpinner {color} size={spinnerSizes[size]} />
|
|
141
|
+
{@render children?.()}
|
|
142
|
+
</div>
|
|
143
|
+
{:else}
|
|
144
|
+
{@render children?.()}
|
|
145
|
+
{/if}
|
|
134
146
|
</ButtonPrimitive.Root>
|
|
135
147
|
{/if}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { getFieldContext } from '../common/context.svelte.js';
|
|
3
|
+
import Field from '../components/Form/Field.svelte';
|
|
4
|
+
import Input from '../components/Form/Input.svelte';
|
|
5
|
+
import Icon from '../components/Icon/Icon.svelte';
|
|
6
|
+
import IconButton from '../components/IconButton/IconButton.svelte';
|
|
7
|
+
import type { SelectCommonProps, SelectItem } from '../types.js';
|
|
8
|
+
import { cleanClass, generateId } from '../utils.js';
|
|
9
|
+
import { mdiArrowDown, mdiArrowUp, mdiCheck, mdiUnfoldMoreHorizontal } from '@mdi/js';
|
|
10
|
+
import { Select } from 'bits-ui';
|
|
11
|
+
import { tv } from 'tailwind-variants';
|
|
12
|
+
|
|
13
|
+
type T = SelectItem;
|
|
14
|
+
|
|
15
|
+
type Props = {
|
|
16
|
+
multiple?: boolean;
|
|
17
|
+
values?: T[];
|
|
18
|
+
asLabel?: (items: T[]) => string;
|
|
19
|
+
onChange?: (values: T[]) => void;
|
|
20
|
+
} & SelectCommonProps<T>;
|
|
21
|
+
|
|
22
|
+
let {
|
|
23
|
+
data,
|
|
24
|
+
shape,
|
|
25
|
+
color = 'primary',
|
|
26
|
+
size = 'medium',
|
|
27
|
+
multiple = false,
|
|
28
|
+
values = $bindable([]),
|
|
29
|
+
onChange,
|
|
30
|
+
asLabel = (options: T[]) => options.map(({ label }) => label).join(', '),
|
|
31
|
+
placeholder,
|
|
32
|
+
class: className,
|
|
33
|
+
}: Props = $props();
|
|
34
|
+
|
|
35
|
+
const asOptions = (items: string[] | T[]) => {
|
|
36
|
+
return items.map((item) => {
|
|
37
|
+
if (typeof item === 'string') {
|
|
38
|
+
return { value: item, label: item } as T;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const label = item.label ?? item.value;
|
|
42
|
+
return { ...item, label };
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const options = $derived(asOptions(data));
|
|
47
|
+
|
|
48
|
+
const {
|
|
49
|
+
label,
|
|
50
|
+
readOnly = false,
|
|
51
|
+
required = false,
|
|
52
|
+
invalid = false,
|
|
53
|
+
disabled = false,
|
|
54
|
+
} = $derived(getFieldContext());
|
|
55
|
+
|
|
56
|
+
const labelStyles = tv({
|
|
57
|
+
base: '',
|
|
58
|
+
variants: {
|
|
59
|
+
size: {
|
|
60
|
+
tiny: 'text-xs',
|
|
61
|
+
small: 'text-sm',
|
|
62
|
+
medium: 'text-md',
|
|
63
|
+
large: 'text-lg',
|
|
64
|
+
giant: 'text-xl',
|
|
65
|
+
},
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const id = generateId();
|
|
70
|
+
const inputId = `input-${id}`;
|
|
71
|
+
const labelId = `label-${id}`;
|
|
72
|
+
|
|
73
|
+
const selectedLabel = $derived(asLabel(values));
|
|
74
|
+
|
|
75
|
+
let inputRef = $state<HTMLElement | null>(null);
|
|
76
|
+
let contentRef = $state<HTMLElement | null>(null);
|
|
77
|
+
let ref = $state<HTMLElement | null>(null);
|
|
78
|
+
|
|
79
|
+
$effect(() => {
|
|
80
|
+
if (ref && contentRef) {
|
|
81
|
+
contentRef.style.width = `${ref.clientWidth}px`;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const findOption = (value: string) => options.find((option) => option.value === value);
|
|
86
|
+
|
|
87
|
+
const onValueChange = (items: string[] | string) => {
|
|
88
|
+
values = multiple
|
|
89
|
+
? ((items as string[]).map(findOption) as T[])
|
|
90
|
+
: [findOption(items as string) as T].filter(Boolean);
|
|
91
|
+
|
|
92
|
+
onChange?.(values);
|
|
93
|
+
};
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<div class={cleanClass('flex flex-col gap-1', className)} bind:this={ref}>
|
|
97
|
+
{#if label}
|
|
98
|
+
<label id={labelId} for={inputId} class={labelStyles({ size })}>{label}</label>
|
|
99
|
+
{/if}
|
|
100
|
+
|
|
101
|
+
<Select.Root type={multiple ? 'multiple' : 'single'} {onValueChange}>
|
|
102
|
+
<Select.Trigger
|
|
103
|
+
{disabled}
|
|
104
|
+
class="w-full items-center gap-1 rounded-lg focus-visible:outline-none"
|
|
105
|
+
aria-label={placeholder}
|
|
106
|
+
>
|
|
107
|
+
<Field {readOnly} {required} {disabled} {invalid}>
|
|
108
|
+
<Input
|
|
109
|
+
bind:containerRef={inputRef}
|
|
110
|
+
id={inputId}
|
|
111
|
+
{size}
|
|
112
|
+
{shape}
|
|
113
|
+
{color}
|
|
114
|
+
{placeholder}
|
|
115
|
+
value={selectedLabel}
|
|
116
|
+
readonly
|
|
117
|
+
aria-labelledby={labelId}
|
|
118
|
+
aria-readonly
|
|
119
|
+
>
|
|
120
|
+
{#snippet trailingIcon()}
|
|
121
|
+
<IconButton
|
|
122
|
+
variant="ghost"
|
|
123
|
+
shape="round"
|
|
124
|
+
color="secondary"
|
|
125
|
+
class="m-1"
|
|
126
|
+
icon={mdiUnfoldMoreHorizontal}
|
|
127
|
+
{disabled}
|
|
128
|
+
/>
|
|
129
|
+
{/snippet}
|
|
130
|
+
</Input>
|
|
131
|
+
</Field>
|
|
132
|
+
</Select.Trigger>
|
|
133
|
+
<Select.Portal>
|
|
134
|
+
<Select.Content
|
|
135
|
+
bind:ref={contentRef}
|
|
136
|
+
class="max-h-96 select-none rounded-xl border bg-light py-3 text-dark outline-none"
|
|
137
|
+
sideOffset={10}
|
|
138
|
+
>
|
|
139
|
+
<Select.ScrollUpButton class="flex w-full items-center justify-center">
|
|
140
|
+
<Icon icon={mdiArrowUp} />
|
|
141
|
+
</Select.ScrollUpButton>
|
|
142
|
+
<Select.Viewport>
|
|
143
|
+
{#each options as { value, label, disabled }, i (i + value)}
|
|
144
|
+
<Select.Item
|
|
145
|
+
class={cleanClass(
|
|
146
|
+
'flex h-10 w-full select-none items-center px-5 py-3 text-sm outline-none duration-75 hover:bg-subtle data-[selected]:bg-primary/10 data-[disabled]:opacity-50',
|
|
147
|
+
disabled ? 'cursor-not-allowed' : 'cursor-pointer',
|
|
148
|
+
)}
|
|
149
|
+
{value}
|
|
150
|
+
{label}
|
|
151
|
+
{disabled}
|
|
152
|
+
>
|
|
153
|
+
{#snippet children({ selected })}
|
|
154
|
+
<div
|
|
155
|
+
class="flex cursor-pointer items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-colors"
|
|
156
|
+
>
|
|
157
|
+
<span>{label}</span>
|
|
158
|
+
</div>
|
|
159
|
+
{#if selected}
|
|
160
|
+
<div class="ml-auto">
|
|
161
|
+
<Icon icon={mdiCheck} />
|
|
162
|
+
</div>
|
|
163
|
+
{/if}
|
|
164
|
+
{/snippet}
|
|
165
|
+
</Select.Item>
|
|
166
|
+
{/each}
|
|
167
|
+
</Select.Viewport>
|
|
168
|
+
<Select.ScrollDownButton class="flex w-full items-center justify-center">
|
|
169
|
+
<Icon icon={mdiArrowDown} />
|
|
170
|
+
</Select.ScrollDownButton>
|
|
171
|
+
</Select.Content>
|
|
172
|
+
</Select.Portal>
|
|
173
|
+
</Select.Root>
|
|
174
|
+
</div>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { SelectCommonProps, SelectItem } from '../types.js';
|
|
2
|
+
import { Select } from 'bits-ui';
|
|
3
|
+
declare const Select: import("svelte").Component<{
|
|
4
|
+
multiple?: boolean;
|
|
5
|
+
values?: SelectItem[];
|
|
6
|
+
asLabel?: (items: SelectItem[]) => string;
|
|
7
|
+
onChange?: (values: SelectItem[]) => void;
|
|
8
|
+
} & SelectCommonProps<SelectItem>, {}, "values">;
|
|
9
|
+
export default Select;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Theme } from '../types.js';
|
|
2
|
+
export const theme = $state({ value: Theme.Dark });
|
|
3
|
+
export const syncToDom = () => {
|
|
4
|
+
switch (theme.value) {
|
|
5
|
+
case Theme.Dark: {
|
|
6
|
+
document.body.classList.add('dark');
|
|
7
|
+
break;
|
|
8
|
+
}
|
|
9
|
+
default: {
|
|
10
|
+
document.body.classList.remove('dark');
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
};
|