lutra 0.1.68 → 0.1.69
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/AspectRatio.svelte +19 -9
- package/dist/components/AspectRatio.svelte.d.ts +2 -1
- package/dist/components/Avatar.svelte +5 -8
- package/dist/components/Close.svelte +24 -27
- package/dist/components/Close.svelte.d.ts +2 -0
- package/dist/components/ContextTip.svelte +3 -2
- package/dist/components/Dialog.svelte +38 -0
- package/dist/components/Icon.svelte +2 -2
- package/dist/components/IconButton.svelte +10 -22
- package/dist/components/Image.svelte +2 -2
- package/dist/components/Indicator.svelte +2 -1
- package/dist/components/Inset.svelte +13 -0
- package/dist/components/Layout.svelte +7 -3
- package/dist/components/Layout.svelte.d.ts +3 -2
- package/dist/components/MenuDropdown.svelte +12 -2
- package/dist/components/MenuItem.svelte +30 -14
- package/dist/components/MenuItem.svelte.d.ts +6 -0
- package/dist/components/Modal.svelte +36 -20
- package/dist/components/Popover.svelte +39 -12
- package/dist/components/TabbedContent.svelte +1 -1
- package/dist/components/TabbedContentItem.svelte +14 -0
- package/dist/components/TabbedContentItem.svelte.d.ts +4 -0
- package/dist/components/Table.svelte +69 -0
- package/dist/components/Table.svelte.d.ts +7 -0
- package/dist/components/Tabs.svelte +44 -36
- package/dist/components/Tag.svelte +53 -13
- package/dist/components/Tag.svelte.d.ts +4 -0
- package/dist/components/Theme.svelte +121 -94
- package/dist/components/Theme.svelte.d.ts +7 -6
- package/dist/components/Toast.svelte +11 -8
- package/dist/components/Tooltip.svelte +17 -10
- package/dist/css/1-props.css +64 -51
- package/dist/css/2-init.css +503 -0
- package/dist/css/{2-base.css → 3-base.css} +42 -131
- package/dist/css/{3-typo.css → 4-typo.css} +3 -1
- package/dist/css/lutra.css +7 -6
- package/dist/css/themes/DefaultTheme.css +16 -4
- package/dist/form/Button.svelte +20 -0
- package/dist/form/Button.svelte.d.ts +9 -0
- package/dist/form/Datepicker.svelte +13 -0
- package/dist/form/Datepicker.svelte.d.ts +3 -0
- package/dist/form/FieldContent.svelte +18 -9
- package/dist/form/FieldError.svelte +1 -1
- package/dist/form/Fieldset.svelte +19 -11
- package/dist/form/Form.svelte +137 -63
- package/dist/form/Form.svelte.d.ts +21 -0
- package/dist/form/FormActions.svelte +21 -3
- package/dist/form/FormActions.svelte.d.ts +3 -0
- package/dist/form/FormSection.svelte +22 -20
- package/dist/form/ImageUpload.svelte +50 -30
- package/dist/form/ImageUpload.svelte.d.ts +14 -0
- package/dist/form/Input.svelte +62 -30
- package/dist/form/Input.svelte.d.ts +0 -1
- package/dist/form/InputLength.svelte +5 -5
- package/dist/form/Label.svelte +6 -6
- package/dist/form/LogoUpload.svelte +24 -10
- package/dist/form/Select.svelte +23 -10
- package/dist/form/Select.svelte.d.ts +6 -6
- package/dist/form/Textarea.svelte +11 -1
- package/dist/form/client.svelte.js +0 -2
- package/dist/state/Persisted.svelte.d.ts +6 -0
- package/dist/state/Persisted.svelte.js +29 -0
- package/dist/state/theme.svelte.d.ts +7 -0
- package/dist/state/theme.svelte.js +14 -0
- package/dist/types.d.ts +6 -23
- package/dist/types.js +0 -17
- package/dist/util/color.js +2 -2
- package/package.json +5 -4
- package/dist/config.d.ts +0 -30
- package/dist/config.js +0 -18
- /package/dist/css/{4-layout.css → 5-layout.css} +0 -0
- /package/dist/css/{5-media.css → 6-media.css} +0 -0
|
@@ -1,26 +1,36 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @description
|
|
6
|
+
* A wrapper that enforces an aspect ratio on its children. Accepts numeric, ratio string, or colon-separated formats.
|
|
7
|
+
* @cssprop --aspect-ratio - The aspect ratio of the container. (Default: 16 / 9)
|
|
8
|
+
* @example
|
|
9
|
+
* <AspectRatio ratio="4:3">
|
|
10
|
+
* <img src="photo.jpg" alt="Photo" style="width: 100%; height: 100%; object-fit: cover;" />
|
|
11
|
+
* </AspectRatio>
|
|
12
|
+
*/
|
|
3
13
|
let {
|
|
4
|
-
ratio = 16 / 9,
|
|
14
|
+
ratio = "16 / 9",
|
|
5
15
|
children
|
|
6
16
|
}: {
|
|
7
|
-
ratio
|
|
17
|
+
/** Aspect ratio as a number (e.g. 1.778), a ratio string (e.g. "16 / 9"), or colon-separated (e.g. "16:9"). */
|
|
18
|
+
ratio?: number | string;
|
|
8
19
|
children: Snippet;
|
|
9
20
|
} = $props();
|
|
21
|
+
|
|
22
|
+
const cssRatio = $derived(
|
|
23
|
+
typeof ratio === "string" ? ratio.replace(":", " / ") : ratio
|
|
24
|
+
);
|
|
10
25
|
</script>
|
|
11
26
|
|
|
12
|
-
<div class="AspectRatio" style="--aspect-ratio: {
|
|
27
|
+
<div class="AspectRatio" style="--aspect-ratio: {cssRatio}">
|
|
13
28
|
{@render children()}
|
|
14
29
|
</div>
|
|
15
30
|
|
|
16
31
|
<style>
|
|
17
|
-
@property --aspect-ratio {
|
|
18
|
-
syntax: "<number-percentage> / <number-percentage>";
|
|
19
|
-
inherits: false;
|
|
20
|
-
initial-value: 16 / 9;
|
|
21
|
-
}
|
|
22
32
|
.AspectRatio {
|
|
23
|
-
aspect-ratio: var(--aspect-ratio);
|
|
33
|
+
aspect-ratio: var(--aspect-ratio, 16 / 9);
|
|
24
34
|
}
|
|
25
35
|
</style>
|
|
26
36
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
2
|
type $$ComponentProps = {
|
|
3
|
-
ratio
|
|
3
|
+
/** Aspect ratio as a number (e.g. 1.778), a ratio string (e.g. "16 / 9"), or colon-separated (e.g. "16:9"). */
|
|
4
|
+
ratio?: number | string;
|
|
4
5
|
children: Snippet;
|
|
5
6
|
};
|
|
6
7
|
declare const AspectRatio: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
@@ -9,6 +9,9 @@
|
|
|
9
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
10
|
* @cssprop --border-radius - The border radius of the avatar when cropped as rounded.
|
|
11
11
|
* @cssprop --mask-image - Custom mask image to use for the avatar.
|
|
12
|
+
* @cssprop --size - The size of the avatar. (Default: 3rem)
|
|
13
|
+
* @cssprop --user-color - The background color of the avatar. (Default: #666666)
|
|
14
|
+
* @cssprop --text-color - The text color of the avatar. (Default: rgba(0,0,0,0.85))
|
|
12
15
|
* @example
|
|
13
16
|
* <p>With picture:</p>
|
|
14
17
|
* <Avatar name="Auth70" shape="rounded" src="https://avatars.githubusercontent.com/u/122825113?s=200&v=4" --size="4rem" />
|
|
@@ -84,11 +87,6 @@
|
|
|
84
87
|
.Avatar.circle { border-radius: 50%; }
|
|
85
88
|
.Avatar.square { border-radius: 0; }
|
|
86
89
|
.Avatar.rounded { border-radius: var(--border-radius); }
|
|
87
|
-
.Avatar img {
|
|
88
|
-
block-size: 100%;
|
|
89
|
-
inline-size: 100%;
|
|
90
|
-
object-fit: cover;
|
|
91
|
-
}
|
|
92
90
|
.Avatar .Placeholder {
|
|
93
91
|
display: flex;
|
|
94
92
|
align-items: center;
|
|
@@ -97,9 +95,8 @@
|
|
|
97
95
|
inline-size: 100%;
|
|
98
96
|
background-color: var(--user-color);
|
|
99
97
|
color: var(--text-color);
|
|
100
|
-
|
|
101
|
-
font-
|
|
102
|
-
font-weight: 600;
|
|
98
|
+
font-size: calc(var(--size, 3rem) * 0.4);
|
|
99
|
+
font-weight: var(--font-weight-medium);
|
|
103
100
|
user-select: none;
|
|
104
101
|
}
|
|
105
102
|
</style>
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* @description
|
|
4
|
+
* A close button rendered as an X icon. Can be absolutely positioned in a corner of its containing element.
|
|
5
|
+
* @cssprop --close-padding - Padding around the icon. (Default: var(--space-xs))
|
|
6
|
+
* @cssprop --close-icon-size - Size of the close icon. (Default: max(1.5rem, 1rem))
|
|
7
|
+
* @example
|
|
8
|
+
* <div style="position: relative; padding: 2rem; border: 1px solid gray;">
|
|
9
|
+
* <Close position="top right" onclick={() => alert('closed')} />
|
|
10
|
+
* <p>Content with a close button</p>
|
|
11
|
+
* </div>
|
|
12
|
+
*/
|
|
2
13
|
let {
|
|
3
14
|
onclick,
|
|
4
15
|
position
|
|
5
16
|
}: {
|
|
17
|
+
/** Callback when the close button is clicked. */
|
|
6
18
|
onclick?: (e: MouseEvent) => void;
|
|
19
|
+
/** Absolute position within the parent container. */
|
|
7
20
|
position?: "top left" | "top right" | "bottom left" | "bottom right";
|
|
8
21
|
} = $props();
|
|
9
22
|
</script>
|
|
@@ -18,10 +31,9 @@
|
|
|
18
31
|
<style>
|
|
19
32
|
.Close {
|
|
20
33
|
cursor: pointer;
|
|
21
|
-
padding: var(--close-padding,
|
|
34
|
+
padding: var(--close-padding, var(--space-xs));
|
|
22
35
|
border-radius: 50%;
|
|
23
|
-
color: var(--text-color
|
|
24
|
-
cursor: pointer;
|
|
36
|
+
color: var(--text-color-p);
|
|
25
37
|
pointer-events: auto;
|
|
26
38
|
border: none;
|
|
27
39
|
}
|
|
@@ -40,37 +52,22 @@
|
|
|
40
52
|
svg {
|
|
41
53
|
display: block;
|
|
42
54
|
margin: 0;
|
|
43
|
-
width: max(1.5rem,
|
|
44
|
-
height: max(1.5rem,
|
|
55
|
+
width: var(--close-icon-size, max(1.5rem, 1rem));
|
|
56
|
+
height: var(--close-icon-size, max(1.5rem, 1rem));
|
|
45
57
|
}
|
|
46
58
|
|
|
47
59
|
.Close:hover {
|
|
48
|
-
color: var(--text-color-subtle
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
.Close.top {
|
|
52
|
-
position: absolute;
|
|
53
|
-
top: 0;
|
|
54
|
-
right: 0;
|
|
55
|
-
z-index: 100;
|
|
60
|
+
color: var(--text-color-p-subtle);
|
|
56
61
|
}
|
|
57
62
|
|
|
63
|
+
.Close.top,
|
|
58
64
|
.Close.bottom {
|
|
59
65
|
position: absolute;
|
|
60
|
-
|
|
61
|
-
right: 0;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
.Close.left {
|
|
65
|
-
position: absolute;
|
|
66
|
-
top: 0;
|
|
67
|
-
left: 0;
|
|
68
|
-
right: auto;
|
|
66
|
+
z-index: 100;
|
|
69
67
|
}
|
|
70
68
|
|
|
71
|
-
.Close.right
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
69
|
+
.Close.top.right { inset: 0 0 auto auto; }
|
|
70
|
+
.Close.top.left { inset: 0 auto auto 0; }
|
|
71
|
+
.Close.bottom.right { inset: auto 0 0 auto; }
|
|
72
|
+
.Close.bottom.left { inset: auto auto 0 0; }
|
|
76
73
|
</style>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
type $$ComponentProps = {
|
|
2
|
+
/** Callback when the close button is clicked. */
|
|
2
3
|
onclick?: (e: MouseEvent) => void;
|
|
4
|
+
/** Absolute position within the parent container. */
|
|
3
5
|
position?: "top left" | "top right" | "bottom left" | "bottom right";
|
|
4
6
|
};
|
|
5
7
|
declare const Close: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
</script>
|
|
19
19
|
|
|
20
20
|
<Tooltip {tip}>
|
|
21
|
-
<a href="#contexttip" onclick={(e) => { e.preventDefault(); e.stopPropagation(); }}>
|
|
21
|
+
<a class="ContextTip" href="#contexttip" onclick={(e) => { e.preventDefault(); e.stopPropagation(); }}>
|
|
22
22
|
<Icon icon={Help} --icon-width="16px" --icon-height="16px" --cursor="help" --vertical-align="baseline" />
|
|
23
23
|
</a>
|
|
24
24
|
</Tooltip>
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
justify-content: center;
|
|
36
36
|
}
|
|
37
37
|
a:focus-visible {
|
|
38
|
-
|
|
38
|
+
outline: var(--focus-ring);
|
|
39
39
|
outline-offset: 0px;
|
|
40
|
+
color: var(--focus-ring-color);
|
|
40
41
|
}
|
|
41
42
|
</style>
|
|
@@ -2,8 +2,14 @@
|
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
+
* @description
|
|
5
6
|
* A simple dialog component using the native `<dialog>` element.
|
|
6
7
|
* For more features (buttons, scrim control, etc.), use `<Modal>` instead.
|
|
8
|
+
* Uses modal tokens for styling (background, border, padding, shadow, etc.).
|
|
9
|
+
* @example
|
|
10
|
+
* <Dialog bind:open={showDialog} title="Confirm">
|
|
11
|
+
* <p>Are you sure?</p>
|
|
12
|
+
* </Dialog>
|
|
7
13
|
*/
|
|
8
14
|
let {
|
|
9
15
|
open = $bindable(false),
|
|
@@ -81,6 +87,38 @@
|
|
|
81
87
|
.Dialog[open] {
|
|
82
88
|
display: grid;
|
|
83
89
|
grid-template-rows: auto 1fr;
|
|
90
|
+
opacity: 1;
|
|
91
|
+
translate: 0 0;
|
|
92
|
+
transition:
|
|
93
|
+
opacity var(--transition-duration-fast) ease-out,
|
|
94
|
+
translate var(--transition-duration-fast) ease-out,
|
|
95
|
+
display var(--transition-duration-fast) allow-discrete,
|
|
96
|
+
overlay var(--transition-duration-fast) allow-discrete;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.Dialog[open]::backdrop {
|
|
100
|
+
opacity: 1;
|
|
101
|
+
transition:
|
|
102
|
+
opacity var(--transition-duration-fast) ease-out,
|
|
103
|
+
display var(--transition-duration-fast) allow-discrete,
|
|
104
|
+
overlay var(--transition-duration-fast) allow-discrete;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
@starting-style {
|
|
108
|
+
.Dialog[open] {
|
|
109
|
+
opacity: 0;
|
|
110
|
+
translate: 0 var(--space-xs);
|
|
111
|
+
}
|
|
112
|
+
.Dialog[open]::backdrop {
|
|
113
|
+
opacity: 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@media (prefers-reduced-motion: reduce) {
|
|
118
|
+
.Dialog,
|
|
119
|
+
.Dialog::backdrop {
|
|
120
|
+
transition: none;
|
|
121
|
+
}
|
|
84
122
|
}
|
|
85
123
|
|
|
86
124
|
.DialogHeader {
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
/**
|
|
5
5
|
* @description
|
|
6
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:
|
|
8
|
-
* @cssprop --icon-height - The height of the icon. (Default:
|
|
7
|
+
* @cssprop --icon-width - The width of the icon. (Default: font-size or 1rem)
|
|
8
|
+
* @cssprop --icon-height - The height of the icon. (Default: font-size or 1rem)
|
|
9
9
|
* @cssprop --icon-color - The color of the icon. (Default: var(--text-color, currentColor))
|
|
10
10
|
* @example
|
|
11
11
|
* <script>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* A component that displays an icon with a possible label. It can also have a click event.
|
|
10
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
11
|
* Icon and text color will be inherited from the parent element.
|
|
12
|
-
* @cssprop --padding - The padding of the icon button. (Default:
|
|
12
|
+
* @cssprop --padding - The padding of the icon button. (Default: var(--space-sm))
|
|
13
13
|
* @example
|
|
14
14
|
* <script>
|
|
15
15
|
* import Copy from 'lutra/Icons/Copy.svelte';
|
|
@@ -71,10 +71,11 @@
|
|
|
71
71
|
color: inherit;
|
|
72
72
|
opacity: 1;
|
|
73
73
|
background-color: var(--field-background-color, transparent);
|
|
74
|
-
transition: background-color
|
|
74
|
+
transition: background-color var(--transition-duration-fast);
|
|
75
75
|
border-radius: var(--field-border-radius);
|
|
76
76
|
}
|
|
77
|
-
.IconButton:hover
|
|
77
|
+
.IconButton:hover,
|
|
78
|
+
.IconButton:focus-visible {
|
|
78
79
|
background-color: var(--menu-background-color-hover);
|
|
79
80
|
}
|
|
80
81
|
.IconButton:active {
|
|
@@ -84,37 +85,24 @@
|
|
|
84
85
|
border: none;
|
|
85
86
|
background: none;
|
|
86
87
|
cursor: pointer;
|
|
87
|
-
color: var(--text-color-p, light-dark(black, white));
|
|
88
88
|
}
|
|
89
89
|
.IconMask {
|
|
90
90
|
height: 100%;
|
|
91
|
-
padding-inline: calc(var(--padding,
|
|
92
|
-
padding-block: calc(var(--padding,
|
|
91
|
+
padding-inline: calc(var(--padding, var(--space-sm)) * 0.8);
|
|
92
|
+
padding-block: calc(var(--padding, var(--space-sm)) * 0.8);
|
|
93
93
|
display: inline-grid;
|
|
94
|
-
gap:
|
|
94
|
+
gap: var(--space-xs);
|
|
95
95
|
grid-template: "icon";
|
|
96
96
|
align-items: center;
|
|
97
97
|
}
|
|
98
98
|
.IconMask.mask {
|
|
99
|
-
-webkit-mask-image: linear-gradient(to bottom,
|
|
100
|
-
mask-image: linear-gradient(to bottom,
|
|
99
|
+
-webkit-mask-image: linear-gradient(to bottom, transparent, black 35%, black 65%, transparent);
|
|
100
|
+
mask-image: linear-gradient(to bottom, transparent, black 35%, black 65%, transparent);
|
|
101
101
|
}
|
|
102
102
|
.IconContent {
|
|
103
103
|
grid-area: icon;
|
|
104
104
|
display: inline-flex;
|
|
105
|
-
gap:
|
|
105
|
+
gap: var(--space-xs);
|
|
106
106
|
align-items: center;
|
|
107
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
108
|
</style>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* <Image aspectRatio="16:9" fit="cover" src="https://images.unsplash.com/photo-1712337646541-d0c6f85447f8" alt="An example image" />
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { BROWSER } from 'esm-env';
|
|
13
13
|
import { decode } from "blurhash";
|
|
14
14
|
import { fade } from "svelte/transition";
|
|
15
15
|
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
let decoded = $state(false);
|
|
97
|
-
let loaded = $state(
|
|
97
|
+
let loaded = $state(BROWSER ? false : true);
|
|
98
98
|
|
|
99
99
|
const onload = () => {
|
|
100
100
|
loaded = true;
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
let _label = $derived(isSet ? StatusColors[color as StatusColor] : label ? label : 'status');
|
|
44
44
|
</script>
|
|
45
45
|
|
|
46
|
-
<span role="status" aria-label="{_label}" class="Indicator {color} {motion}" style="--bgColor: {isSet ? 'var(--status-'+color+')' : color};"></span>
|
|
46
|
+
<span role="status" aria-label="{_label}" class="Indicator {color} {motion}" style="--bgColor: {isSet ? 'var(--status-'+color+'-color)' : color};"></span>
|
|
47
47
|
|
|
48
48
|
<style>
|
|
49
49
|
.Indicator {
|
|
@@ -242,6 +242,7 @@
|
|
|
242
242
|
--mask: radial-gradient(circle, rgba(0, 0, 0, 0) 20%, rgba(0, 0, 0, 0) 45%, black 50%, black 100%);
|
|
243
243
|
-webkit-mask-image: var(--mask);
|
|
244
244
|
mask-image: var(--mask);
|
|
245
|
+
mask-size: 100% 100%;
|
|
245
246
|
filter: drop-shadow(0 0 calc(var(--isize) * 0.05) var(--bgColor));
|
|
246
247
|
}
|
|
247
248
|
.Indicator.spin::after,
|
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* @description
|
|
6
|
+
* Negates the padding of a parent container by applying negative margins, allowing child content (e.g. images) to span edge-to-edge.
|
|
7
|
+
* Reads `--inset-block` and `--inset-inline` from the parent context (typically set by MenuItem or similar).
|
|
8
|
+
* @cssprop --inset-block - The block inset to negate. (Default: 0)
|
|
9
|
+
* @cssprop --inset-inline - The inline inset to negate. (Default: 0)
|
|
10
|
+
* @example
|
|
11
|
+
* <div style="padding: 1rem; --inset-block: 1rem; --inset-inline: 1rem;">
|
|
12
|
+
* <Inset>
|
|
13
|
+
* <img src="banner.jpg" alt="Full-bleed banner" />
|
|
14
|
+
* </Inset>
|
|
15
|
+
* </div>
|
|
16
|
+
*/
|
|
4
17
|
let {
|
|
5
18
|
children,
|
|
6
19
|
}: {
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type
|
|
2
|
+
import { getContext, type Snippet } from "svelte";
|
|
3
3
|
import "../css/lutra.css";
|
|
4
4
|
import Theme from "./Theme.svelte";
|
|
5
5
|
import ToastContainer from "./ToastContainer.svelte";
|
|
6
|
+
import { getContextItem, LutraContext, type LutraContextTypeMap, type LutraTheme } from "../types.js";
|
|
7
|
+
|
|
8
|
+
const lutra = getContext<() => LutraContextTypeMap>('lutra');
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* Default layout component that imports styles and provides theming.
|
|
9
12
|
* Includes ToastContainer for toast notifications.
|
|
10
13
|
*/
|
|
11
14
|
let {
|
|
12
|
-
theme,
|
|
15
|
+
theme = lutra()?.[LutraContext.Theme]?.() ?? "system",
|
|
13
16
|
children,
|
|
14
17
|
}: {
|
|
15
18
|
/** The theme to use. Leave empty for auto-detection. */
|
|
16
|
-
theme?:
|
|
19
|
+
theme?: LutraTheme;
|
|
17
20
|
/** The content to display. */
|
|
18
21
|
children: Snippet;
|
|
19
22
|
} = $props();
|
|
23
|
+
|
|
20
24
|
</script>
|
|
21
25
|
|
|
22
26
|
<Theme theme={theme}>
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Snippet } from "svelte";
|
|
2
2
|
import "../css/lutra.css";
|
|
3
|
+
import { type LutraTheme } from "../types.js";
|
|
3
4
|
type $$ComponentProps = {
|
|
4
5
|
/** The theme to use. Leave empty for auto-detection. */
|
|
5
|
-
theme?:
|
|
6
|
+
theme?: LutraTheme;
|
|
6
7
|
/** The content to display. */
|
|
7
8
|
children: Snippet;
|
|
8
9
|
};
|
|
@@ -7,8 +7,18 @@
|
|
|
7
7
|
import { arrowNavigation, matchOnType } from "../util/keyboard.svelte.js";
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
10
|
+
* @description
|
|
11
|
+
* A dropdown menu built on the Popover component. Handles keyboard navigation (arrow keys, type-ahead)
|
|
12
|
+
* and renders MenuItem entries. Uses menu tokens for styling via the Popover's CSS variable overrides.
|
|
13
|
+
* @example
|
|
14
|
+
* <MenuDropdown
|
|
15
|
+
* trigger="Options"
|
|
16
|
+
* items={[
|
|
17
|
+
* { type: 'item', text: 'Edit', onclick: () => {} },
|
|
18
|
+
* { type: 'divider' },
|
|
19
|
+
* { type: 'item', text: 'Delete', color: 'alert', onclick: () => {} },
|
|
20
|
+
* ]}
|
|
21
|
+
* />
|
|
12
22
|
*/
|
|
13
23
|
let {
|
|
14
24
|
open = $bindable(false),
|
|
@@ -3,6 +3,17 @@
|
|
|
3
3
|
import MenuItemContent from "./MenuItemContent.svelte";
|
|
4
4
|
import type { MenuItem as Item } from "./MenuTypes.js";
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* @description
|
|
8
|
+
* A single menu item within a menu list. Supports item, header, text, and divider types.
|
|
9
|
+
* Items can be links, buttons, or custom rendered content. Keyboard navigation is handled by the parent MenuDropdown.
|
|
10
|
+
* @cssprop --menu-item-font-size - Font size of the menu item. (Default: var(--font-size-sm))
|
|
11
|
+
* @cssprop --menu-item-padding-block - Block padding of the menu item. (Default: var(--space-xs))
|
|
12
|
+
* @cssprop --menu-item-padding-inline - Inline padding of the menu item. (Default: var(--space-md))
|
|
13
|
+
* @cssprop --menu-item-gap - Gap between icon and text within the item. (Default: var(--space-sm))
|
|
14
|
+
* @cssprop --menu-item-margin - Top margin of the first item. (Default: var(--space-xs))
|
|
15
|
+
* @cssprop --menu-shortcut-font-size - Font size of the keyboard shortcut label. (Default: max(0.75em, 9px))
|
|
16
|
+
*/
|
|
6
17
|
let {
|
|
7
18
|
item,
|
|
8
19
|
index,
|
|
@@ -11,11 +22,17 @@
|
|
|
11
22
|
keyboardHasFocus,
|
|
12
23
|
shape = 'default',
|
|
13
24
|
}: {
|
|
25
|
+
/** The menu item data object. */
|
|
14
26
|
item: Item;
|
|
27
|
+
/** The index of this item in the menu list. */
|
|
15
28
|
index: number;
|
|
29
|
+
/** Callback when the mouse enters the item. */
|
|
16
30
|
onmouseover?: (e: MouseEvent, item: Item, index: number) => void;
|
|
31
|
+
/** Callback when the item is selected. */
|
|
17
32
|
onselect?: (item: Item, index: number) => void;
|
|
33
|
+
/** Whether keyboard navigation is active, suppressing mouse hover styles. */
|
|
18
34
|
keyboardHasFocus?: boolean;
|
|
35
|
+
/** The border-radius shape of the item. */
|
|
19
36
|
shape?: 'default' | 'rounded' | 'pill';
|
|
20
37
|
} = $props();
|
|
21
38
|
|
|
@@ -104,20 +121,19 @@
|
|
|
104
121
|
li .Item,
|
|
105
122
|
li .Header,
|
|
106
123
|
li .Text {
|
|
107
|
-
font-size: var(--font-size,
|
|
124
|
+
font-size: var(--menu-item-font-size, var(--font-size-sm));
|
|
108
125
|
text-align: left;
|
|
109
|
-
padding-block:
|
|
110
|
-
padding-inline:
|
|
126
|
+
padding-block: var(--menu-item-padding-block, var(--space-xs));
|
|
127
|
+
padding-inline: var(--menu-item-padding-inline, var(--space-md));
|
|
111
128
|
display: inline-flex;
|
|
112
129
|
align-items: center;
|
|
113
130
|
justify-content: space-between;
|
|
114
131
|
width: 100%;
|
|
115
|
-
color: inherit;
|
|
116
132
|
text-decoration: none;
|
|
117
133
|
color: var(--color);
|
|
118
|
-
--inset-block:
|
|
119
|
-
--inset-inline:
|
|
120
|
-
border-radius:
|
|
134
|
+
--inset-block: var(--menu-item-padding-block, var(--space-xs));
|
|
135
|
+
--inset-inline: var(--menu-item-padding-inline, var(--space-md));
|
|
136
|
+
border-radius: 0;
|
|
121
137
|
white-space: nowrap;
|
|
122
138
|
}
|
|
123
139
|
|
|
@@ -128,7 +144,7 @@
|
|
|
128
144
|
}
|
|
129
145
|
|
|
130
146
|
li .Header {
|
|
131
|
-
font-weight:
|
|
147
|
+
font-weight: var(--font-weight-medium);
|
|
132
148
|
}
|
|
133
149
|
|
|
134
150
|
li:not(.keyboardHasFocus) .Item:not(.Custom):hover,
|
|
@@ -146,7 +162,7 @@
|
|
|
146
162
|
}
|
|
147
163
|
|
|
148
164
|
li .Item span.Shortcut {
|
|
149
|
-
font-size: max(0.75em, 9px);
|
|
165
|
+
font-size: var(--menu-shortcut-font-size, max(0.75em, 9px));
|
|
150
166
|
text-align: right;
|
|
151
167
|
color: var(--menu-text-color-subtle);
|
|
152
168
|
white-space: nowrap;
|
|
@@ -157,23 +173,23 @@
|
|
|
157
173
|
}
|
|
158
174
|
|
|
159
175
|
li.divider {
|
|
160
|
-
padding-block:
|
|
176
|
+
padding-block: var(--menu-item-padding-block, var(--space-xs));
|
|
161
177
|
}
|
|
162
178
|
|
|
163
179
|
hr {
|
|
164
180
|
display: block;
|
|
165
181
|
border: none;
|
|
166
182
|
margin: 0;
|
|
167
|
-
border-top:
|
|
183
|
+
border-top: var(--menu-border-size) var(--menu-border-style) var(--menu-border-color);
|
|
168
184
|
}
|
|
169
185
|
|
|
170
186
|
li:first-child[data-type="item"] {
|
|
171
|
-
margin-block-start: var(--menu-item-margin,
|
|
187
|
+
margin-block-start: var(--menu-item-margin, var(--space-xs));
|
|
172
188
|
}
|
|
173
189
|
|
|
174
|
-
@media (pointer:
|
|
190
|
+
@media (pointer: coarse) {
|
|
175
191
|
li .Item span.Shortcut {
|
|
176
192
|
display: none;
|
|
177
|
-
}
|
|
193
|
+
}
|
|
178
194
|
}
|
|
179
195
|
</style>
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { MenuItem as Item } from "./MenuTypes.js";
|
|
2
2
|
type $$ComponentProps = {
|
|
3
|
+
/** The menu item data object. */
|
|
3
4
|
item: Item;
|
|
5
|
+
/** The index of this item in the menu list. */
|
|
4
6
|
index: number;
|
|
7
|
+
/** Callback when the mouse enters the item. */
|
|
5
8
|
onmouseover?: (e: MouseEvent, item: Item, index: number) => void;
|
|
9
|
+
/** Callback when the item is selected. */
|
|
6
10
|
onselect?: (item: Item, index: number) => void;
|
|
11
|
+
/** Whether keyboard navigation is active, suppressing mouse hover styles. */
|
|
7
12
|
keyboardHasFocus?: boolean;
|
|
13
|
+
/** The border-radius shape of the item. */
|
|
8
14
|
shape?: 'default' | 'rounded' | 'pill';
|
|
9
15
|
};
|
|
10
16
|
declare const MenuItem: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
@@ -5,30 +5,20 @@
|
|
|
5
5
|
import type { ModalButton } from "./ModalTypes.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* ```svelte
|
|
8
|
+
* @description
|
|
9
|
+
* A modal component using the native `<dialog>` element. Supports trigger-based, controlled, and auto-show patterns.
|
|
10
|
+
* Uses modal tokens for styling. The `contained` prop can be set via context.
|
|
11
|
+
* @cssprop --modal-width - Width of the modal. (Default: fit-content)
|
|
12
|
+
* @cssprop --modal-min-width - Minimum width. (Default: auto)
|
|
13
|
+
* @cssprop --modal-max-width - Maximum width. (Default: 40rem)
|
|
14
|
+
* @cssprop --modal-max-height - Maximum height. (Default: 80svh)
|
|
15
|
+
* @example
|
|
17
16
|
* <Modal bind:open={showModal}>
|
|
18
17
|
* {#snippet content(close)}
|
|
19
18
|
* <p>Modal content</p>
|
|
19
|
+
* <button onclick={close}>Close</button>
|
|
20
20
|
* {/snippet}
|
|
21
21
|
* </Modal>
|
|
22
|
-
* ```
|
|
23
|
-
*
|
|
24
|
-
* **Pattern C: Auto-show (no trigger)**
|
|
25
|
-
* ```svelte
|
|
26
|
-
* {#if shouldShow}
|
|
27
|
-
* <Modal open onclose={() => shouldShow = false}>
|
|
28
|
-
* {#snippet content(close)}...{/snippet}
|
|
29
|
-
* </Modal>
|
|
30
|
-
* {/if}
|
|
31
|
-
* ```
|
|
32
22
|
*/
|
|
33
23
|
let {
|
|
34
24
|
open = $bindable(false),
|
|
@@ -241,6 +231,31 @@
|
|
|
241
231
|
dialog.Modal[open] {
|
|
242
232
|
display: grid;
|
|
243
233
|
grid-template-rows: 1fr auto;
|
|
234
|
+
opacity: 1;
|
|
235
|
+
translate: 0 0;
|
|
236
|
+
transition:
|
|
237
|
+
opacity var(--transition-duration-fast) ease-out,
|
|
238
|
+
translate var(--transition-duration-fast) ease-out,
|
|
239
|
+
display var(--transition-duration-fast) allow-discrete,
|
|
240
|
+
overlay var(--transition-duration-fast) allow-discrete;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
dialog.Modal[open]::backdrop {
|
|
244
|
+
opacity: 1;
|
|
245
|
+
transition:
|
|
246
|
+
opacity var(--transition-duration-fast) ease-out,
|
|
247
|
+
display var(--transition-duration-fast) allow-discrete,
|
|
248
|
+
overlay var(--transition-duration-fast) allow-discrete;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
@starting-style {
|
|
252
|
+
dialog.Modal[open] {
|
|
253
|
+
opacity: 0;
|
|
254
|
+
translate: 0 var(--space-xs);
|
|
255
|
+
}
|
|
256
|
+
dialog.Modal[open]::backdrop {
|
|
257
|
+
opacity: 0;
|
|
258
|
+
}
|
|
244
259
|
}
|
|
245
260
|
|
|
246
261
|
dialog.Modal.contained {
|
|
@@ -306,7 +321,8 @@
|
|
|
306
321
|
}
|
|
307
322
|
|
|
308
323
|
@media (prefers-reduced-motion: reduce) {
|
|
309
|
-
dialog.Modal
|
|
324
|
+
dialog.Modal,
|
|
325
|
+
dialog.Modal::backdrop {
|
|
310
326
|
transition: none;
|
|
311
327
|
}
|
|
312
328
|
}
|