lutra 0.1.0 → 0.1.4
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/Avatar.svelte +105 -0
- package/dist/components/Avatar.svelte.d.ts +14 -0
- package/dist/components/Close.svelte +76 -0
- package/dist/components/Close.svelte.d.ts +7 -0
- package/dist/components/ContextTip.svelte +41 -0
- package/dist/components/ContextTip.svelte.d.ts +7 -0
- package/dist/components/Icon.svelte +62 -0
- package/dist/components/Icon.svelte.d.ts +8 -0
- package/dist/components/IconButton.svelte +120 -0
- package/dist/components/IconButton.svelte.d.ts +16 -0
- package/dist/components/Image.svelte +172 -0
- package/dist/components/Image.svelte.d.ts +56 -0
- package/dist/components/Indicator.svelte +387 -0
- package/dist/components/Indicator.svelte.d.ts +12 -0
- package/dist/components/Inset.svelte +23 -0
- package/dist/components/Inset.svelte.d.ts +7 -0
- package/dist/components/Layout.svelte +2 -1
- package/dist/components/MenuDropdown.svelte +195 -0
- package/dist/components/MenuDropdown.svelte.d.ts +16 -0
- package/dist/components/MenuItem.svelte +155 -0
- package/dist/components/MenuItem.svelte.d.ts +11 -0
- package/dist/components/MenuItemContent.svelte +25 -0
- package/dist/components/MenuItemContent.svelte.d.ts +10 -0
- package/dist/components/MenuTypes.d.ts +72 -0
- package/dist/components/MenuTypes.js +1 -0
- package/dist/components/Modal.svelte +149 -0
- package/dist/components/Modal.svelte.d.ts +16 -0
- package/dist/components/Notification.svelte +115 -0
- package/dist/components/Notification.svelte.d.ts +12 -0
- package/dist/components/Overlay.svelte +31 -0
- package/dist/components/Overlay.svelte.d.ts +14 -0
- package/dist/components/OverlayContainer.svelte +31 -0
- package/dist/components/OverlayContainer.svelte.d.ts +18 -0
- package/dist/components/OverlayLayer.svelte +168 -0
- package/dist/components/OverlayLayer.svelte.d.ts +8 -0
- package/dist/components/TabbedContent.svelte +74 -0
- package/dist/components/TabbedContent.svelte.d.ts +11 -0
- package/dist/components/TabbedContentItem.svelte +33 -0
- package/dist/components/TabbedContentItem.svelte.d.ts +10 -0
- package/dist/components/Table.svelte +41 -0
- package/dist/components/Table.svelte.d.ts +13 -0
- package/dist/components/Tabs.svelte +216 -0
- package/dist/components/Tabs.svelte.d.ts +20 -0
- package/dist/components/Tag.svelte +120 -0
- package/dist/components/Tag.svelte.d.ts +21 -0
- package/dist/components/Theme.svelte +32 -14
- package/dist/components/Tooltip.svelte +8 -8
- package/dist/components/UIContent.svelte +19 -0
- package/dist/components/UIContent.svelte.d.ts +7 -0
- package/dist/components/index.d.ts +28 -0
- package/dist/components/index.js +29 -0
- package/dist/components/notifications.svelte.d.ts +21 -0
- package/dist/components/notifications.svelte.js +30 -0
- package/dist/components/overlays.svelte.d.ts +36 -0
- package/dist/components/overlays.svelte.js +44 -0
- package/dist/css/1-props.css +389 -724
- package/dist/css/2-base.css +257 -123
- package/dist/css/3-typo.css +75 -34
- package/dist/css/4-layout.css +364 -1
- package/dist/css/5-media.css +106 -11
- package/dist/css/lutra.css +2 -1
- package/dist/css/themes/DefaultTheme.css +209 -0
- package/dist/form/Button.svelte +58 -0
- package/dist/form/Button.svelte.d.ts +15 -0
- package/dist/form/Datepicker.svelte +311 -0
- package/dist/form/Datepicker.svelte.d.ts +9 -0
- package/dist/form/FieldContent.svelte +178 -0
- package/dist/form/FieldContent.svelte.d.ts +21 -0
- package/dist/form/FieldError.svelte +24 -0
- package/dist/form/FieldError.svelte.d.ts +7 -0
- package/dist/form/Fieldset.svelte +103 -0
- package/dist/form/Fieldset.svelte.d.ts +20 -0
- package/dist/form/Form.svelte +220 -0
- package/dist/form/Form.svelte.d.ts +38 -0
- package/dist/form/FormActions.svelte +80 -0
- package/dist/form/FormActions.svelte.d.ts +9 -0
- package/dist/form/FormSection.svelte +96 -0
- package/dist/form/FormSection.svelte.d.ts +9 -0
- package/dist/form/ImageUpload.svelte +299 -0
- package/dist/form/ImageUpload.svelte.d.ts +20 -0
- package/dist/form/Input.svelte +444 -0
- package/dist/form/Input.svelte.d.ts +108 -0
- package/dist/form/InputLength.svelte +42 -0
- package/dist/form/InputLength.svelte.d.ts +9 -0
- package/dist/form/Label.svelte +88 -0
- package/dist/form/Label.svelte.d.ts +16 -0
- package/dist/form/LogoUpload.svelte +115 -0
- package/dist/form/LogoUpload.svelte.d.ts +18 -0
- package/dist/form/Select.svelte +186 -0
- package/dist/form/Select.svelte.d.ts +59 -0
- package/dist/form/Textarea.svelte +265 -0
- package/dist/form/Textarea.svelte.d.ts +95 -0
- package/dist/form/Toggle.svelte +4 -0
- package/dist/form/Toggle.svelte.d.ts +18 -0
- package/dist/form/client.svelte.d.ts +45 -0
- package/dist/form/client.svelte.js +102 -0
- package/dist/form/form.d.ts +55 -0
- package/dist/form/form.js +345 -0
- package/dist/form/index.d.ts +17 -0
- package/dist/form/index.js +17 -0
- package/dist/form/types.d.ts +55 -0
- package/dist/form/types.js +1 -0
- package/dist/icons/IconAlert.svelte +3 -0
- package/dist/icons/IconAlert.svelte.d.ts +26 -0
- package/dist/icons/IconCopy.svelte +3 -0
- package/dist/icons/IconCopy.svelte.d.ts +26 -0
- package/dist/icons/IconDone.svelte +3 -0
- package/dist/icons/IconDone.svelte.d.ts +26 -0
- package/dist/icons/IconError.svelte +3 -0
- package/dist/icons/IconError.svelte.d.ts +26 -0
- package/dist/icons/IconHelp.svelte +3 -0
- package/dist/icons/IconHelp.svelte.d.ts +26 -0
- package/dist/icons/IconHide.svelte +3 -0
- package/dist/icons/IconHide.svelte.d.ts +26 -0
- package/dist/icons/IconInfo.svelte +3 -0
- package/dist/icons/IconInfo.svelte.d.ts +26 -0
- package/dist/icons/IconLink.svelte +3 -0
- package/dist/icons/IconLink.svelte.d.ts +26 -0
- package/dist/icons/IconMenuBurger.svelte +3 -0
- package/dist/icons/IconMenuBurger.svelte.d.ts +26 -0
- package/dist/icons/IconMenuDots.svelte +3 -0
- package/dist/icons/IconMenuDots.svelte.d.ts +26 -0
- package/dist/icons/IconSearch.svelte +3 -0
- package/dist/icons/IconSearch.svelte.d.ts +26 -0
- package/dist/icons/IconShow.svelte +3 -0
- package/dist/icons/IconShow.svelte.d.ts +26 -0
- package/dist/icons/IconSuccess.svelte +3 -0
- package/dist/icons/IconSuccess.svelte.d.ts +26 -0
- package/dist/icons/IconWarning.svelte +3 -0
- package/dist/icons/IconWarning.svelte.d.ts +26 -0
- package/dist/icons/index.d.ts +14 -0
- package/dist/icons/index.js +14 -0
- package/dist/index.d.ts +3 -5
- package/dist/index.js +3 -5
- package/dist/util/StringOrComponent.svelte +20 -0
- package/dist/util/StringOrComponent.svelte.d.ts +8 -0
- package/dist/util/StringOrSnippet.svelte +16 -0
- package/dist/util/StringOrSnippet.svelte.d.ts +8 -0
- package/dist/util/attr.d.ts +5 -0
- package/dist/util/attr.js +21 -0
- package/dist/util/color.d.ts +51 -0
- package/dist/util/color.js +97 -0
- package/dist/util/dom.d.ts +15 -0
- package/dist/util/dom.js +73 -0
- package/dist/util/keyboard.svelte.d.ts +22 -0
- package/dist/util/keyboard.svelte.js +161 -0
- package/dist/util/locale.d.ts +1 -0
- package/dist/util/locale.js +47 -0
- package/dist/util/settings.d.ts +4 -0
- package/dist/util/settings.js +1 -0
- package/package.json +20 -11
- package/dist/css/0-layers.css +0 -1
@@ -0,0 +1,105 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import type { Snippet } from 'svelte';
|
3
|
+
import { hexRelativeLuminance, stringToColor } from '../util/color.js';
|
4
|
+
import Image from './Image.svelte';
|
5
|
+
|
6
|
+
/**
|
7
|
+
* @description
|
8
|
+
* An avatar is a user profile photo or a logo. It can be a placeholder or a URL to an image. If there is no image, the first letter of the name will be used as a placeholder.
|
9
|
+
* You can pick from three crop styles: circle, square, or rounded. The color of the placeholder is based on the name, making each visually distinct from each other.
|
10
|
+
* @cssprop --border-radius - The border radius of the avatar when cropped as rounded.
|
11
|
+
* @cssprop --mask-image - Custom mask image to use for the avatar.
|
12
|
+
* @example
|
13
|
+
* <p>With picture:</p>
|
14
|
+
* <Avatar name="Auth70" shape="rounded" src="https://avatars.githubusercontent.com/u/122825113?s=200&v=4" --size="4rem" />
|
15
|
+
* <p>Without picture:</p>
|
16
|
+
* <Avatar name="Moxie" shape="square" />
|
17
|
+
* <Avatar name="Napoleon" shape="rounded" />
|
18
|
+
* <Avatar name="Byron" shape="circle" />
|
19
|
+
*/
|
20
|
+
let {
|
21
|
+
src,
|
22
|
+
srcset,
|
23
|
+
alt,
|
24
|
+
color,
|
25
|
+
size = '3rem',
|
26
|
+
name = "N/A",
|
27
|
+
shape = 'circle',
|
28
|
+
children,
|
29
|
+
}: {
|
30
|
+
src?: string;
|
31
|
+
srcset?: string;
|
32
|
+
alt?: string;
|
33
|
+
color?: string;
|
34
|
+
size?: string;
|
35
|
+
name?: string;
|
36
|
+
shape?: 'circle' | 'square' | 'rounded';
|
37
|
+
children?: Snippet;
|
38
|
+
} = $props();
|
39
|
+
|
40
|
+
const userColor = (src || srcset) ? '#666666' : color ?? stringToColor(name);
|
41
|
+
const luminance = hexRelativeLuminance(userColor);
|
42
|
+
const textColor = luminance < 0.5 ? 'rgba(255,255,255,0.85)' : 'rgba(0,0,0,0.85)';
|
43
|
+
const firstCharacter = [...name][0]; // supports multi-byte characters. name[0] would not.
|
44
|
+
</script>
|
45
|
+
|
46
|
+
<figure class="Avatar {shape}" style="--user-color: {userColor}; --text-color: {textColor}; --size: {size}">
|
47
|
+
{#if src}
|
48
|
+
<Image
|
49
|
+
src={src}
|
50
|
+
srcset={srcset}
|
51
|
+
alt={alt ?? name}
|
52
|
+
fit="cover"
|
53
|
+
--width={size}
|
54
|
+
--height={size}
|
55
|
+
/>
|
56
|
+
{:else}
|
57
|
+
<div class="Placeholder">
|
58
|
+
{#if children}
|
59
|
+
{@render children()}
|
60
|
+
{:else}
|
61
|
+
<span>{firstCharacter}</span>
|
62
|
+
{/if}
|
63
|
+
</div>
|
64
|
+
{/if}
|
65
|
+
</figure>
|
66
|
+
|
67
|
+
<style>
|
68
|
+
.Avatar {
|
69
|
+
display: flex;
|
70
|
+
align-items: center;
|
71
|
+
justify-content: center;
|
72
|
+
margin: 0;
|
73
|
+
block-size: var(--size, 3rem);
|
74
|
+
inline-size: var(--size, 3rem);
|
75
|
+
overflow: clip;
|
76
|
+
background-color: var(--user-color);
|
77
|
+
mask-image: var(--mask-image, none);
|
78
|
+
mask-size: 100% 100%;
|
79
|
+
mask-repeat: no-repeat;
|
80
|
+
-webkit-mask-image: var(--mask-image, none);
|
81
|
+
-webkit-mask-size: 100% 100%;
|
82
|
+
-webkit-mask-repeat: no-repeat;
|
83
|
+
}
|
84
|
+
.Avatar.circle { border-radius: 50%; }
|
85
|
+
.Avatar.square { border-radius: 0; }
|
86
|
+
.Avatar.rounded { border-radius: var(--border-radius); }
|
87
|
+
.Avatar img {
|
88
|
+
block-size: 100%;
|
89
|
+
inline-size: 100%;
|
90
|
+
object-fit: cover;
|
91
|
+
}
|
92
|
+
.Avatar .Placeholder {
|
93
|
+
display: flex;
|
94
|
+
align-items: center;
|
95
|
+
justify-content: center;
|
96
|
+
block-size: 100%;
|
97
|
+
inline-size: 100%;
|
98
|
+
background-color: var(--user-color);
|
99
|
+
color: var(--text-color);
|
100
|
+
user-select: none;
|
101
|
+
font-size: 1rem;
|
102
|
+
font-weight: 600;
|
103
|
+
user-select: none;
|
104
|
+
}
|
105
|
+
</style>
|
@@ -0,0 +1,14 @@
|
|
1
|
+
import type { Snippet } from 'svelte';
|
2
|
+
type $$ComponentProps = {
|
3
|
+
src?: string;
|
4
|
+
srcset?: string;
|
5
|
+
alt?: string;
|
6
|
+
color?: string;
|
7
|
+
size?: string;
|
8
|
+
name?: string;
|
9
|
+
shape?: 'circle' | 'square' | 'rounded';
|
10
|
+
children?: Snippet;
|
11
|
+
};
|
12
|
+
declare const Avatar: import("svelte").Component<$$ComponentProps, {}, "">;
|
13
|
+
type Avatar = ReturnType<typeof Avatar>;
|
14
|
+
export default Avatar;
|
@@ -0,0 +1,76 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
let {
|
3
|
+
onclick,
|
4
|
+
position
|
5
|
+
}: {
|
6
|
+
onclick?: (e: MouseEvent) => void;
|
7
|
+
position?: "top left" | "top right" | "bottom left" | "bottom right";
|
8
|
+
} = $props();
|
9
|
+
</script>
|
10
|
+
|
11
|
+
<button class="Close {position}" {onclick} aria-label="Close">
|
12
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
13
|
+
<line x1="18" y1="6" x2="6" y2="18"></line>
|
14
|
+
<line x1="6" y1="6" x2="18" y2="18"></line>
|
15
|
+
</svg>
|
16
|
+
</button>
|
17
|
+
|
18
|
+
<style>
|
19
|
+
.Close {
|
20
|
+
cursor: pointer;
|
21
|
+
padding: var(--close-padding, 0.5rem);
|
22
|
+
border-radius: 50%;
|
23
|
+
color: var(--text-color, light-dark(black, white));
|
24
|
+
cursor: pointer;
|
25
|
+
pointer-events: auto;
|
26
|
+
border: none;
|
27
|
+
}
|
28
|
+
|
29
|
+
button {
|
30
|
+
background-color: transparent;
|
31
|
+
border: none;
|
32
|
+
padding: 0;
|
33
|
+
margin: 0;
|
34
|
+
font: inherit;
|
35
|
+
color: inherit;
|
36
|
+
text-align: inherit;
|
37
|
+
display: inline-block;
|
38
|
+
}
|
39
|
+
|
40
|
+
svg {
|
41
|
+
display: block;
|
42
|
+
margin: 0;
|
43
|
+
width: max(1.5rem, 16px);
|
44
|
+
height: max(1.5rem, 16px);
|
45
|
+
}
|
46
|
+
|
47
|
+
.Close:hover {
|
48
|
+
color: var(--text-color-subtle, light-dark(rgba(0, 0, 0, 0.5), rgba(255, 255, 255, 0.5)));
|
49
|
+
}
|
50
|
+
|
51
|
+
.Close.top {
|
52
|
+
position: absolute;
|
53
|
+
top: 0;
|
54
|
+
right: 0;
|
55
|
+
z-index: 100;
|
56
|
+
}
|
57
|
+
|
58
|
+
.Close.bottom {
|
59
|
+
position: absolute;
|
60
|
+
bottom: 0;
|
61
|
+
right: 0;
|
62
|
+
}
|
63
|
+
|
64
|
+
.Close.left {
|
65
|
+
position: absolute;
|
66
|
+
top: 0;
|
67
|
+
left: 0;
|
68
|
+
right: auto;
|
69
|
+
}
|
70
|
+
|
71
|
+
.Close.right {
|
72
|
+
position: absolute;
|
73
|
+
top: 0;
|
74
|
+
right: 0;
|
75
|
+
}
|
76
|
+
</style>
|
@@ -0,0 +1,7 @@
|
|
1
|
+
type $$ComponentProps = {
|
2
|
+
onclick?: (e: MouseEvent) => void;
|
3
|
+
position?: "top left" | "top right" | "bottom left" | "bottom right";
|
4
|
+
};
|
5
|
+
declare const Close: import("svelte").Component<$$ComponentProps, {}, "">;
|
6
|
+
type Close = ReturnType<typeof Close>;
|
7
|
+
export default Close;
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import type { Snippet } from "svelte";
|
3
|
+
import Tooltip from "./Tooltip.svelte";
|
4
|
+
import Help from "../icons/IconHelp.svelte";
|
5
|
+
import Icon from "./Icon.svelte";
|
6
|
+
|
7
|
+
/**
|
8
|
+
* @description
|
9
|
+
* A component that displays a context tip.
|
10
|
+
* @example
|
11
|
+
* <ContextTip tip="This is a context tip" />
|
12
|
+
*/
|
13
|
+
let {
|
14
|
+
tip
|
15
|
+
}: {
|
16
|
+
tip: string | Snippet;
|
17
|
+
} = $props();
|
18
|
+
</script>
|
19
|
+
|
20
|
+
<Tooltip {tip}>
|
21
|
+
<a href="#contexttip" onclick={(e) => { e.preventDefault(); e.stopPropagation(); }}>
|
22
|
+
<Icon icon={Help} --icon-width="16px" --icon-height="16px" --cursor="help" --vertical-align="baseline" />
|
23
|
+
</a>
|
24
|
+
</Tooltip>
|
25
|
+
|
26
|
+
<style>
|
27
|
+
a {
|
28
|
+
border-radius: 50%;
|
29
|
+
color: var(--overlay-color, light-dark(black, white));
|
30
|
+
height: 16px;
|
31
|
+
width: 16px;
|
32
|
+
padding: 0;
|
33
|
+
display: inline-flex;
|
34
|
+
align-items: center;
|
35
|
+
justify-content: center;
|
36
|
+
}
|
37
|
+
a:focus-visible {
|
38
|
+
color: var(--focus-ring-color);
|
39
|
+
outline-offset: 0px;
|
40
|
+
}
|
41
|
+
</style>
|
@@ -0,0 +1,62 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import type { Component } from "svelte";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* @description
|
6
|
+
* A component that displays an icon. It can be an image url or a component. The icon will be centered in the container.
|
7
|
+
* @cssprop --icon-width - The width of the icon. (Default: var(--font-size, 1em))
|
8
|
+
* @cssprop --icon-height - The height of the icon. (Default: var(--font-size, 1em))
|
9
|
+
* @cssprop --icon-color - The color of the icon. (Default: var(--text-color, currentColor))
|
10
|
+
* @example
|
11
|
+
* <script>
|
12
|
+
* import Copy from 'lutra/Icons/Copy.svelte';
|
13
|
+
* import Link from 'lutra/Icons/Link.svelte';
|
14
|
+
* <\/script>
|
15
|
+
* <Icon icon={Link} alt="Link icon" />
|
16
|
+
* <Icon icon={Copy} alt="Copy icon" />
|
17
|
+
*/
|
18
|
+
|
19
|
+
let {
|
20
|
+
icon,
|
21
|
+
alt,
|
22
|
+
}: {
|
23
|
+
icon: string | Component;
|
24
|
+
alt?: string,
|
25
|
+
} = $props();
|
26
|
+
|
27
|
+
let Icon = icon as Component;
|
28
|
+
|
29
|
+
</script>
|
30
|
+
|
31
|
+
<span class="Icon" aria-label="icon">
|
32
|
+
{#if typeof icon === "string"}
|
33
|
+
<img src={icon} alt={alt ? alt : 'Icon'} width="100%" height="100%" />
|
34
|
+
{:else}
|
35
|
+
<Icon />
|
36
|
+
{/if}
|
37
|
+
</span>
|
38
|
+
|
39
|
+
<style>
|
40
|
+
.Icon {
|
41
|
+
display: inline-flex;
|
42
|
+
align-items: center;
|
43
|
+
justify-content: center;
|
44
|
+
font-size: 1em;
|
45
|
+
line-height: inherit;
|
46
|
+
width: max(
|
47
|
+
var(--icon-width,
|
48
|
+
var(--icon-height, 1rem)
|
49
|
+
), 16px);
|
50
|
+
height: max(var(--icon-height, auto), 16px);
|
51
|
+
color: var(--icon-color, currentColor);
|
52
|
+
overflow: clip;
|
53
|
+
vertical-align: var(--vertical-align, text-bottom);
|
54
|
+
cursor: var(--cursor, default);
|
55
|
+
}
|
56
|
+
img,
|
57
|
+
.Icon :global(svg) {
|
58
|
+
width: 100%;
|
59
|
+
height: auto;
|
60
|
+
display: block;
|
61
|
+
}
|
62
|
+
</style>
|
@@ -0,0 +1,120 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
import type { Component, Snippet } from "svelte";
|
3
|
+
import Icon from './Icon.svelte';
|
4
|
+
import { fly } from "svelte/transition";
|
5
|
+
import { popBezier, popBezierInverse } from "../util/transitions.js";
|
6
|
+
|
7
|
+
/**
|
8
|
+
* @description
|
9
|
+
* A component that displays an icon with a possible label. It can also have a click event.
|
10
|
+
* The button has a padding of 0.75em by default to make it easier to tap on mobile devices. The padding can be changed using the `--padding` CSS variable.
|
11
|
+
* Icon and text color will be inherited from the parent element.
|
12
|
+
* @cssprop --padding - The padding of the icon button. (Default: 0.75em)
|
13
|
+
* @example
|
14
|
+
* <script>
|
15
|
+
* import Copy from 'lutra/Icons/Copy.svelte';
|
16
|
+
* import Link from 'lutra/Icons/Link.svelte';
|
17
|
+
* <\/script>
|
18
|
+
* <IconButton icon={Link} onclick={() => alert('clicked')} />
|
19
|
+
* <IconButton icon={Copy} onclick={() => alert('clicked')}>Copy</IconButton>
|
20
|
+
*/
|
21
|
+
let {
|
22
|
+
icon,
|
23
|
+
disabled,
|
24
|
+
children,
|
25
|
+
onclick,
|
26
|
+
mask = true
|
27
|
+
}: {
|
28
|
+
/** The icon to display. */
|
29
|
+
icon: string | Component;
|
30
|
+
/** The children to display. */
|
31
|
+
children?: Snippet,
|
32
|
+
/** The onclick event. */
|
33
|
+
onclick?: (event: MouseEvent) => void,
|
34
|
+
/** Whether the button is disabled. */
|
35
|
+
disabled?: boolean,
|
36
|
+
/** Whether to mask the content. */
|
37
|
+
mask?: boolean
|
38
|
+
} = $props();
|
39
|
+
|
40
|
+
</script>
|
41
|
+
|
42
|
+
{#snippet inside()}
|
43
|
+
<span class="IconMask" class:mask>
|
44
|
+
{#key icon}
|
45
|
+
<span class="IconContent" in:fly={{ duration: 250, y: -32, easing: popBezier }} out:fly={{ duration: 250, y: 32, easing: popBezierInverse }}>
|
46
|
+
<Icon {icon} --cursor="pointer" />
|
47
|
+
{#if children}
|
48
|
+
<span class="Text">
|
49
|
+
{@render children()}
|
50
|
+
</span>
|
51
|
+
{/if}
|
52
|
+
</span>
|
53
|
+
{/key}
|
54
|
+
</span>
|
55
|
+
{/snippet}
|
56
|
+
|
57
|
+
{#if onclick}
|
58
|
+
<button type="button" {disabled} class="IconButton" {onclick}>
|
59
|
+
{@render inside()}
|
60
|
+
</button>
|
61
|
+
{:else}
|
62
|
+
<span class="IconButton">
|
63
|
+
{@render inside()}
|
64
|
+
</span>
|
65
|
+
{/if}
|
66
|
+
|
67
|
+
<style>
|
68
|
+
.IconButton {
|
69
|
+
display: inline-flex;
|
70
|
+
align-items: stretch;
|
71
|
+
color: inherit;
|
72
|
+
opacity: 1;
|
73
|
+
background-color: var(--field-background-color, transparent);
|
74
|
+
transition: background-color 0.04s;
|
75
|
+
border-radius: var(--field-border-radius);
|
76
|
+
}
|
77
|
+
.IconButton:hover {
|
78
|
+
background-color: var(--menu-background-color-hover);
|
79
|
+
}
|
80
|
+
.IconButton:active {
|
81
|
+
scale: 1;
|
82
|
+
}
|
83
|
+
button.IconButton {
|
84
|
+
border: none;
|
85
|
+
background: none;
|
86
|
+
cursor: pointer;
|
87
|
+
color: var(--text-color-p, light-dark(black, white));
|
88
|
+
}
|
89
|
+
.IconMask {
|
90
|
+
height: 100%;
|
91
|
+
padding-inline: calc(var(--padding, 0.75em) * 0.8);
|
92
|
+
padding-block: calc(var(--padding, 0.75em) * 0.8);
|
93
|
+
display: inline-grid;
|
94
|
+
gap: 0.5rem;
|
95
|
+
grid-template: "icon";
|
96
|
+
align-items: center;
|
97
|
+
}
|
98
|
+
.IconMask.mask {
|
99
|
+
-webkit-mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 65%, rgba(0, 0, 0, 0));
|
100
|
+
mask-image: linear-gradient(to bottom, rgba(0, 0, 0, 0), rgba(0, 0, 0, 1) 35%, rgba(0, 0, 0, 1) 65%, rgba(0, 0, 0, 0));
|
101
|
+
}
|
102
|
+
.IconContent {
|
103
|
+
grid-area: icon;
|
104
|
+
display: inline-flex;
|
105
|
+
gap: 0.5rem;
|
106
|
+
align-items: center;
|
107
|
+
}
|
108
|
+
@media(max-width: 960px) {
|
109
|
+
.IconMask {
|
110
|
+
padding-inline: calc(var(--padding, 0.75em) * 0.75);
|
111
|
+
padding-block: calc(var(--padding, 0.75em) * 0.75);
|
112
|
+
}
|
113
|
+
}
|
114
|
+
@media(max-width: 320px) {
|
115
|
+
.IconMask {
|
116
|
+
padding-inline: calc(var(--padding, 0.75em) * 0.6);
|
117
|
+
padding-block: calc(var(--padding, 0.75em) * 0.6);
|
118
|
+
}
|
119
|
+
}
|
120
|
+
</style>
|
@@ -0,0 +1,16 @@
|
|
1
|
+
import type { Component, Snippet } from "svelte";
|
2
|
+
type $$ComponentProps = {
|
3
|
+
/** The icon to display. */
|
4
|
+
icon: string | Component;
|
5
|
+
/** The children to display. */
|
6
|
+
children?: Snippet;
|
7
|
+
/** The onclick event. */
|
8
|
+
onclick?: (event: MouseEvent) => void;
|
9
|
+
/** Whether the button is disabled. */
|
10
|
+
disabled?: boolean;
|
11
|
+
/** Whether to mask the content. */
|
12
|
+
mask?: boolean;
|
13
|
+
};
|
14
|
+
declare const IconButton: Component<$$ComponentProps, {}, "">;
|
15
|
+
type IconButton = ReturnType<typeof IconButton>;
|
16
|
+
export default IconButton;
|
@@ -0,0 +1,172 @@
|
|
1
|
+
<script lang="ts">
|
2
|
+
/**
|
3
|
+
* @description
|
4
|
+
* An image component that can be used to display images with different aspect ratios and fit modes.
|
5
|
+
* Can also decode a BlurHash.
|
6
|
+
* @cssprop --width - The width of the image.
|
7
|
+
* @cssprop --height - The height of the image.
|
8
|
+
* @example
|
9
|
+
* <Image aspectRatio="16:9" fit="cover" src="https://images.unsplash.com/photo-1712337646541-d0c6f85447f8" alt="An example image" />
|
10
|
+
*/
|
11
|
+
|
12
|
+
import { browser } from "$app/environment";
|
13
|
+
import { decode } from "blurhash";
|
14
|
+
import { fade } from "svelte/transition";
|
15
|
+
|
16
|
+
let {
|
17
|
+
hash,
|
18
|
+
width,
|
19
|
+
height,
|
20
|
+
aspectRatio,
|
21
|
+
fit,
|
22
|
+
src,
|
23
|
+
srcset,
|
24
|
+
alt,
|
25
|
+
style = '',
|
26
|
+
class: className,
|
27
|
+
...rest
|
28
|
+
}: {
|
29
|
+
/**
|
30
|
+
* @description
|
31
|
+
* A BlurHash string to decode into an image.
|
32
|
+
*/
|
33
|
+
hash?: string;
|
34
|
+
/**
|
35
|
+
* @description
|
36
|
+
* The aspect ratio of the image.
|
37
|
+
*/
|
38
|
+
aspectRatio?: string;
|
39
|
+
/**
|
40
|
+
* @description
|
41
|
+
* The fit mode of the image.
|
42
|
+
* @default 'cover'
|
43
|
+
*/
|
44
|
+
fit?: 'cover' | 'contain' | 'scale-down';
|
45
|
+
/**
|
46
|
+
* @description
|
47
|
+
* The source URL of the image.
|
48
|
+
*/
|
49
|
+
src?: string;
|
50
|
+
/**
|
51
|
+
* @description
|
52
|
+
* The source URL of the image (with srcset).
|
53
|
+
*/
|
54
|
+
srcset?: string;
|
55
|
+
/**
|
56
|
+
* @description
|
57
|
+
* The alt text of the image.
|
58
|
+
*/
|
59
|
+
alt?: string;
|
60
|
+
/**
|
61
|
+
* @description
|
62
|
+
* The width of the image.
|
63
|
+
*/
|
64
|
+
width?: string | number;
|
65
|
+
/**
|
66
|
+
* @description
|
67
|
+
* The height of the image.
|
68
|
+
*/
|
69
|
+
height?: string | number;
|
70
|
+
/**
|
71
|
+
* @description
|
72
|
+
* Additional style attributes for the image.
|
73
|
+
*/
|
74
|
+
style?: string;
|
75
|
+
/**
|
76
|
+
* @description
|
77
|
+
* Additional classes to apply to the image.
|
78
|
+
*/
|
79
|
+
class?: string;
|
80
|
+
} = $props();
|
81
|
+
|
82
|
+
let canvas: HTMLCanvasElement | null = $state(null);
|
83
|
+
let el: HTMLImageElement | null = $state(null);
|
84
|
+
let elWidth = $state(32);
|
85
|
+
let elHeight = $state(32);
|
86
|
+
|
87
|
+
if(!src && srcset) {
|
88
|
+
const [src1] = srcset.split(", ");
|
89
|
+
src = src1.split(" ")[0];
|
90
|
+
}
|
91
|
+
|
92
|
+
if(aspectRatio) {
|
93
|
+
style += ` aspect-ratio: ${aspectRatio};`;
|
94
|
+
}
|
95
|
+
|
96
|
+
let decoded = $state(false);
|
97
|
+
let loaded = $state(browser ? false : true);
|
98
|
+
|
99
|
+
const onload = () => {
|
100
|
+
loaded = true;
|
101
|
+
};
|
102
|
+
|
103
|
+
$effect(() => {
|
104
|
+
if(hash) {
|
105
|
+
setTimeout(() => {
|
106
|
+
const pixels = decode(hash!, elWidth, elHeight);
|
107
|
+
const ctx = canvas!.getContext("2d");
|
108
|
+
const imageData = ctx!.createImageData(elWidth, elHeight);
|
109
|
+
imageData.data.set(pixels);
|
110
|
+
ctx!.putImageData(imageData, 0, 0);
|
111
|
+
decoded = true;
|
112
|
+
}, 0);
|
113
|
+
}
|
114
|
+
});
|
115
|
+
|
116
|
+
</script>
|
117
|
+
|
118
|
+
<div class="Image" class:loaded>
|
119
|
+
<img
|
120
|
+
bind:this={el}
|
121
|
+
bind:clientWidth={elWidth}
|
122
|
+
bind:clientHeight={elHeight}
|
123
|
+
{onload}
|
124
|
+
{src}
|
125
|
+
{srcset}
|
126
|
+
{alt}
|
127
|
+
width={width ? width : '100%'}
|
128
|
+
height={height ? height : '100%'}
|
129
|
+
{style}
|
130
|
+
class="{className} {fit}"
|
131
|
+
{...rest}
|
132
|
+
/>
|
133
|
+
{#if !loaded && hash}
|
134
|
+
<canvas class:decoded out:fade={{ duration: 100 }} width={elWidth} height={elHeight} bind:this={canvas}></canvas>
|
135
|
+
{/if}
|
136
|
+
</div>
|
137
|
+
|
138
|
+
<style>
|
139
|
+
.Image {
|
140
|
+
position: relative;
|
141
|
+
display: grid;
|
142
|
+
grid-template-areas: "img";
|
143
|
+
width: var(--width, 100%);
|
144
|
+
height: var(--height, 100%);
|
145
|
+
}
|
146
|
+
canvas {
|
147
|
+
z-index: 2;
|
148
|
+
opacity: 0;
|
149
|
+
width: 100%;
|
150
|
+
grid-area: img;
|
151
|
+
transition: opacity var(--transition-speed-slow);
|
152
|
+
}
|
153
|
+
canvas.decoded {
|
154
|
+
opacity: 1;
|
155
|
+
}
|
156
|
+
img {
|
157
|
+
z-index: 1;
|
158
|
+
grid-area: img;
|
159
|
+
display: inline-block;
|
160
|
+
vertical-align: middle;
|
161
|
+
opacity: 0;
|
162
|
+
transition: opacity var(--transition-speed-slow);
|
163
|
+
width: var(--width, 100%);
|
164
|
+
height: var(--height, 100%);
|
165
|
+
}
|
166
|
+
img.cover { object-fit: cover; }
|
167
|
+
img.contain { object-fit: contain; }
|
168
|
+
img.scale-down { object-fit: scale-down; }
|
169
|
+
.Image.loaded img {
|
170
|
+
opacity: 1;
|
171
|
+
}
|
172
|
+
</style>
|
@@ -0,0 +1,56 @@
|
|
1
|
+
type $$ComponentProps = {
|
2
|
+
/**
|
3
|
+
* @description
|
4
|
+
* A BlurHash string to decode into an image.
|
5
|
+
*/
|
6
|
+
hash?: string;
|
7
|
+
/**
|
8
|
+
* @description
|
9
|
+
* The aspect ratio of the image.
|
10
|
+
*/
|
11
|
+
aspectRatio?: string;
|
12
|
+
/**
|
13
|
+
* @description
|
14
|
+
* The fit mode of the image.
|
15
|
+
* @default 'cover'
|
16
|
+
*/
|
17
|
+
fit?: 'cover' | 'contain' | 'scale-down';
|
18
|
+
/**
|
19
|
+
* @description
|
20
|
+
* The source URL of the image.
|
21
|
+
*/
|
22
|
+
src?: string;
|
23
|
+
/**
|
24
|
+
* @description
|
25
|
+
* The source URL of the image (with srcset).
|
26
|
+
*/
|
27
|
+
srcset?: string;
|
28
|
+
/**
|
29
|
+
* @description
|
30
|
+
* The alt text of the image.
|
31
|
+
*/
|
32
|
+
alt?: string;
|
33
|
+
/**
|
34
|
+
* @description
|
35
|
+
* The width of the image.
|
36
|
+
*/
|
37
|
+
width?: string | number;
|
38
|
+
/**
|
39
|
+
* @description
|
40
|
+
* The height of the image.
|
41
|
+
*/
|
42
|
+
height?: string | number;
|
43
|
+
/**
|
44
|
+
* @description
|
45
|
+
* Additional style attributes for the image.
|
46
|
+
*/
|
47
|
+
style?: string;
|
48
|
+
/**
|
49
|
+
* @description
|
50
|
+
* Additional classes to apply to the image.
|
51
|
+
*/
|
52
|
+
class?: string;
|
53
|
+
};
|
54
|
+
declare const Image: import("svelte").Component<$$ComponentProps, {}, "">;
|
55
|
+
type Image = ReturnType<typeof Image>;
|
56
|
+
export default Image;
|