lutra 0.1.0 → 0.1.5

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.
Files changed (154) hide show
  1. package/dist/components/Avatar.svelte +105 -0
  2. package/dist/components/Avatar.svelte.d.ts +14 -0
  3. package/dist/components/Close.svelte +76 -0
  4. package/dist/components/Close.svelte.d.ts +7 -0
  5. package/dist/components/ContextTip.svelte +41 -0
  6. package/dist/components/ContextTip.svelte.d.ts +7 -0
  7. package/dist/components/Icon.svelte +62 -0
  8. package/dist/components/Icon.svelte.d.ts +8 -0
  9. package/dist/components/IconButton.svelte +120 -0
  10. package/dist/components/IconButton.svelte.d.ts +16 -0
  11. package/dist/components/Image.svelte +172 -0
  12. package/dist/components/Image.svelte.d.ts +56 -0
  13. package/dist/components/Indicator.svelte +387 -0
  14. package/dist/components/Indicator.svelte.d.ts +12 -0
  15. package/dist/components/Inset.svelte +23 -0
  16. package/dist/components/Inset.svelte.d.ts +7 -0
  17. package/dist/components/Layout.svelte +2 -1
  18. package/dist/components/MenuDropdown.svelte +195 -0
  19. package/dist/components/MenuDropdown.svelte.d.ts +16 -0
  20. package/dist/components/MenuItem.svelte +159 -0
  21. package/dist/components/MenuItem.svelte.d.ts +11 -0
  22. package/dist/components/MenuItemContent.svelte +25 -0
  23. package/dist/components/MenuItemContent.svelte.d.ts +10 -0
  24. package/dist/components/MenuTypes.d.ts +79 -0
  25. package/dist/components/MenuTypes.js +1 -0
  26. package/dist/components/Modal.svelte +149 -0
  27. package/dist/components/Modal.svelte.d.ts +16 -0
  28. package/dist/components/Notification.svelte +115 -0
  29. package/dist/components/Notification.svelte.d.ts +12 -0
  30. package/dist/components/Overlay.svelte +31 -0
  31. package/dist/components/Overlay.svelte.d.ts +14 -0
  32. package/dist/components/OverlayContainer.svelte +31 -0
  33. package/dist/components/OverlayContainer.svelte.d.ts +18 -0
  34. package/dist/components/OverlayLayer.svelte +168 -0
  35. package/dist/components/OverlayLayer.svelte.d.ts +8 -0
  36. package/dist/components/PageContent.svelte +4 -82
  37. package/dist/components/PageContent.svelte.d.ts +0 -31
  38. package/dist/components/TabbedContent.svelte +74 -0
  39. package/dist/components/TabbedContent.svelte.d.ts +11 -0
  40. package/dist/components/TabbedContentItem.svelte +33 -0
  41. package/dist/components/TabbedContentItem.svelte.d.ts +10 -0
  42. package/dist/components/Table.svelte +41 -0
  43. package/dist/components/Table.svelte.d.ts +13 -0
  44. package/dist/components/Tabs.svelte +216 -0
  45. package/dist/components/Tabs.svelte.d.ts +20 -0
  46. package/dist/components/Tag.svelte +120 -0
  47. package/dist/components/Tag.svelte.d.ts +21 -0
  48. package/dist/components/Theme.svelte +32 -14
  49. package/dist/components/Tooltip.svelte +8 -8
  50. package/dist/components/UIContent.svelte +19 -0
  51. package/dist/components/UIContent.svelte.d.ts +7 -0
  52. package/dist/components/index.d.ts +28 -0
  53. package/dist/components/index.js +29 -0
  54. package/dist/components/notifications.svelte.d.ts +21 -0
  55. package/dist/components/notifications.svelte.js +30 -0
  56. package/dist/components/overlays.svelte.d.ts +36 -0
  57. package/dist/components/overlays.svelte.js +44 -0
  58. package/dist/css/1-props.css +389 -724
  59. package/dist/css/2-base.css +257 -123
  60. package/dist/css/3-typo.css +74 -34
  61. package/dist/css/4-layout.css +364 -1
  62. package/dist/css/5-media.css +106 -11
  63. package/dist/css/lutra.css +2 -1
  64. package/dist/css/themes/DefaultTheme.css +209 -0
  65. package/dist/form/Button.svelte +58 -0
  66. package/dist/form/Button.svelte.d.ts +15 -0
  67. package/dist/form/Datepicker.svelte +311 -0
  68. package/dist/form/Datepicker.svelte.d.ts +9 -0
  69. package/dist/form/FieldContent.svelte +178 -0
  70. package/dist/form/FieldContent.svelte.d.ts +21 -0
  71. package/dist/form/FieldError.svelte +24 -0
  72. package/dist/form/FieldError.svelte.d.ts +7 -0
  73. package/dist/form/Fieldset.svelte +103 -0
  74. package/dist/form/Fieldset.svelte.d.ts +20 -0
  75. package/dist/form/Form.svelte +220 -0
  76. package/dist/form/Form.svelte.d.ts +38 -0
  77. package/dist/form/FormActions.svelte +80 -0
  78. package/dist/form/FormActions.svelte.d.ts +9 -0
  79. package/dist/form/FormSection.svelte +96 -0
  80. package/dist/form/FormSection.svelte.d.ts +9 -0
  81. package/dist/form/ImageUpload.svelte +299 -0
  82. package/dist/form/ImageUpload.svelte.d.ts +20 -0
  83. package/dist/form/Input.svelte +444 -0
  84. package/dist/form/Input.svelte.d.ts +108 -0
  85. package/dist/form/InputLength.svelte +42 -0
  86. package/dist/form/InputLength.svelte.d.ts +9 -0
  87. package/dist/form/Label.svelte +88 -0
  88. package/dist/form/Label.svelte.d.ts +16 -0
  89. package/dist/form/LogoUpload.svelte +115 -0
  90. package/dist/form/LogoUpload.svelte.d.ts +18 -0
  91. package/dist/form/Select.svelte +186 -0
  92. package/dist/form/Select.svelte.d.ts +59 -0
  93. package/dist/form/Textarea.svelte +265 -0
  94. package/dist/form/Textarea.svelte.d.ts +95 -0
  95. package/dist/form/Toggle.svelte +4 -0
  96. package/dist/form/Toggle.svelte.d.ts +18 -0
  97. package/dist/form/client.svelte.d.ts +45 -0
  98. package/dist/form/client.svelte.js +102 -0
  99. package/dist/form/form.d.ts +55 -0
  100. package/dist/form/form.js +345 -0
  101. package/dist/form/index.d.ts +17 -0
  102. package/dist/form/index.js +17 -0
  103. package/dist/form/types.d.ts +55 -0
  104. package/dist/form/types.js +1 -0
  105. package/dist/icons/IconAlert.svelte +3 -0
  106. package/dist/icons/IconAlert.svelte.d.ts +26 -0
  107. package/dist/icons/IconCopy.svelte +3 -0
  108. package/dist/icons/IconCopy.svelte.d.ts +26 -0
  109. package/dist/icons/IconDone.svelte +3 -0
  110. package/dist/icons/IconDone.svelte.d.ts +26 -0
  111. package/dist/icons/IconError.svelte +3 -0
  112. package/dist/icons/IconError.svelte.d.ts +26 -0
  113. package/dist/icons/IconHelp.svelte +3 -0
  114. package/dist/icons/IconHelp.svelte.d.ts +26 -0
  115. package/dist/icons/IconHide.svelte +3 -0
  116. package/dist/icons/IconHide.svelte.d.ts +26 -0
  117. package/dist/icons/IconInfo.svelte +3 -0
  118. package/dist/icons/IconInfo.svelte.d.ts +26 -0
  119. package/dist/icons/IconLink.svelte +3 -0
  120. package/dist/icons/IconLink.svelte.d.ts +26 -0
  121. package/dist/icons/IconMenuBurger.svelte +3 -0
  122. package/dist/icons/IconMenuBurger.svelte.d.ts +26 -0
  123. package/dist/icons/IconMenuDots.svelte +3 -0
  124. package/dist/icons/IconMenuDots.svelte.d.ts +26 -0
  125. package/dist/icons/IconSearch.svelte +3 -0
  126. package/dist/icons/IconSearch.svelte.d.ts +26 -0
  127. package/dist/icons/IconShow.svelte +3 -0
  128. package/dist/icons/IconShow.svelte.d.ts +26 -0
  129. package/dist/icons/IconSuccess.svelte +3 -0
  130. package/dist/icons/IconSuccess.svelte.d.ts +26 -0
  131. package/dist/icons/IconWarning.svelte +3 -0
  132. package/dist/icons/IconWarning.svelte.d.ts +26 -0
  133. package/dist/icons/index.d.ts +14 -0
  134. package/dist/icons/index.js +14 -0
  135. package/dist/index.d.ts +3 -5
  136. package/dist/index.js +3 -5
  137. package/dist/util/StringOrComponent.svelte +20 -0
  138. package/dist/util/StringOrComponent.svelte.d.ts +8 -0
  139. package/dist/util/StringOrSnippet.svelte +16 -0
  140. package/dist/util/StringOrSnippet.svelte.d.ts +8 -0
  141. package/dist/util/attr.d.ts +5 -0
  142. package/dist/util/attr.js +21 -0
  143. package/dist/util/color.d.ts +51 -0
  144. package/dist/util/color.js +97 -0
  145. package/dist/util/dom.d.ts +15 -0
  146. package/dist/util/dom.js +73 -0
  147. package/dist/util/keyboard.svelte.d.ts +22 -0
  148. package/dist/util/keyboard.svelte.js +161 -0
  149. package/dist/util/locale.d.ts +1 -0
  150. package/dist/util/locale.js +47 -0
  151. package/dist/util/settings.d.ts +4 -0
  152. package/dist/util/settings.js +1 -0
  153. package/package.json +20 -11
  154. package/dist/css/0-layers.css +0 -1
@@ -0,0 +1,387 @@
1
+ <script lang="ts">
2
+ import { isStatusColor, StatusColors, type StatusColorOrString, type StatusColor } from "../util/color.js";
3
+
4
+ /**
5
+ * @description
6
+ * A status indicator. Can be used to show the state of something, like a task or a process.
7
+ * @aria role="status"
8
+ * @aria aria-label="" - The name of the status color if used, otherwise pass a `label` prop.
9
+ * @cssprop --size - The size of the indicator. (Default: 0.75em)
10
+ * @cssprop --animation-duration - The duration of the animation when motion is applied. Clamps lower value to 0.5s and upper value to 3s. (Default: 1.2s)
11
+ * @cssprop --animation-iteration-count - The number of times the animation should repeat. (Default: infinite)
12
+ * @cssprop --margin-inline - The inline margin for the indicator. (Default: calc(var(--size) * 0.75))
13
+ * @example
14
+ * <h5>Without motion</h5>
15
+ * <p>
16
+ * <Indicator /> <Indicator color="ok" /> <Indicator color="alert" /> <Indicator color="warn" /> <Indicator color="info" /> <Indicator color="task" /> Static indicators
17
+ * </p>
18
+ * <h5>With motion</h5>
19
+ * <p>
20
+ * <Indicator motion="tunnel" color="blue" --size="1em" />
21
+ * <Indicator motion="blink" color="alert" --animation-duration="1s" --size="1em" />
22
+ * <Indicator motion="spin" color="ok" --size="1em" />
23
+ * <Indicator motion="bulge" color="alert" --size="1em" />
24
+ * <Indicator motion="highlight" color="warn" --animation-duration="2s" --size="1em" />
25
+ * <Indicator motion="pulse" color="task" --size="1em" />
26
+ * <Indicator motion="typing" color="default" --size="1em" />
27
+ * Moving indicators
28
+ * </p>
29
+ */
30
+ let {
31
+ color = "default",
32
+ motion,
33
+ label
34
+ }: {
35
+ /** The color of the tag. Select from default status colors or provide a CSS Color value. */
36
+ color?: StatusColorOrString;
37
+ /** Possible motion to apply to the indicator. */
38
+ motion?: "pulse" | "spin" | "blink" | "highlight" | "bulge" | "tunnel" | "typing";
39
+ /** ARIA label to use when a custom color is applied */
40
+ label?: string;
41
+ } = $props();
42
+ let isSet = $derived(isStatusColor(color));
43
+ let _label = $derived(isSet ? StatusColors[color as StatusColor] : label ? label : 'status');
44
+ </script>
45
+
46
+ <span role="status" aria-label="{_label}" class="Indicator {color} {motion}" style="--bgColor: {isSet ? 'var(--status-'+color+')' : color};"></span>
47
+
48
+ <style>
49
+ .Indicator {
50
+ --isize: var(--size, 0.75em);
51
+ --icount: var(--animation-iteration-count, infinite);
52
+ --bwidth: min(3px, calc(var(--isize) * 0.15));
53
+ display: inline-block;
54
+ position: relative;
55
+ vertical-align: middle;
56
+ margin-inline: var(--margin-inline, 0 calc(var(--isize) * 0.75));
57
+ background: var(--bgColor);
58
+ block-size: var(--isize);
59
+ inline-size: var(--isize);
60
+ border-radius: 50%;
61
+ mask-size: 0% 0%;
62
+ --dur: clamp(0.5s, var(--animation-duration, 1.2s), 3s);
63
+ transition: all calc(var(--dur) / 2), width 0s, height 0s, margin 0s;
64
+ margin-block-start: -2px;
65
+ animation-name: none;
66
+ animation-duration: var(--dur);
67
+ animation-iteration-count: var(--icount);
68
+ border-width: var(--bwidth);
69
+ border-style: solid;
70
+ border-color: transparent;
71
+ box-sizing: border-box;
72
+ }
73
+ .Indicator::after,
74
+ .Indicator::before {
75
+ box-sizing: border-box;
76
+ content: "";
77
+ position: absolute;
78
+ top: 0%;
79
+ left: 0;
80
+ width: 100%;
81
+ height: 100%;
82
+ border-radius: 50%;
83
+ border-width: var(--bwidth);
84
+ border-style: solid;
85
+ border-color: transparent;
86
+ animation-name: none;
87
+ animation-duration: var(--dur);
88
+ animation-iteration-count: var(--icount);
89
+ }
90
+
91
+ /**
92
+ * Tunnel
93
+ */
94
+
95
+ .Indicator.tunnel {
96
+ background: transparent;
97
+ border-color: var(--bgColor);
98
+ }
99
+ .Indicator.tunnel::after,
100
+ .Indicator.tunnel::before {
101
+ animation-name: tunnel;
102
+ animation-timing-function: ease-in;
103
+ border-width: calc(var(--bwidth) * 2);
104
+ animation-duration: calc(var(--dur) * 2);
105
+ }
106
+ .Indicator.tunnel::before {
107
+ animation-delay: calc(-1 * var(--dur) * 0.95);
108
+ }
109
+ @keyframes tunnel {
110
+ 0% {
111
+ border-color: var(--bgColor);
112
+ transform: scale(0.1);
113
+ opacity: 0;
114
+ }
115
+ 10% {
116
+ opacity: 1;
117
+ }
118
+ 75% {
119
+ opacity: 1;
120
+ }
121
+ 100% {
122
+ transform: scale(1.5);
123
+ opacity: 0;
124
+ }
125
+ }
126
+
127
+ /**
128
+ * Blink
129
+ */
130
+
131
+ .Indicator.blink {
132
+ animation-name: blink;
133
+ animation-timing-function: ease-in-out;
134
+ }
135
+ @keyframes blink {
136
+ 0% {
137
+ opacity: 1;
138
+ transform: scale(1);
139
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.15) var(--bgColor));
140
+ }
141
+ 25% {
142
+ opacity: 0.5;
143
+ transform: scale(0.95);
144
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.1) transparent);
145
+ }
146
+ 50% {
147
+ opacity: 0.5;
148
+ transform: scale(0.95);
149
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.1) transparent);
150
+ }
151
+ 75% {
152
+ opacity: 1;
153
+ transform: scale(1);
154
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.15) var(--bgColor));
155
+ }
156
+ 100% {
157
+ opacity: 1;
158
+ transform: scale(1);
159
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.15) var(--bgColor));
160
+ }
161
+ }
162
+
163
+ /**
164
+ * Bulge
165
+ */
166
+
167
+ .Indicator.bulge {
168
+ animation-name: bulge;
169
+ animation-timing-function: ease-in-out;
170
+ }
171
+ @keyframes bulge {
172
+ 0% {
173
+ transform: scale(0.85);
174
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.05) var(--bgColor)) brightness(1) saturate(1);
175
+ }
176
+ 50% {
177
+ transform: scale(1);
178
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.1) var(--bgColor)) brightness(1.15) saturate(1.15);
179
+ }
180
+ 100% {
181
+ transform: scale(0.85);
182
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.05) var(--bgColor)) brightness(1) saturate(1);
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Highlight
188
+ */
189
+
190
+ .Indicator.highlight {
191
+ animation-name: hilite2;
192
+ animation-timing-function:cubic-bezier(0.445, 0.05, 0.55, 0.95);
193
+ border-width: 0;
194
+ }
195
+
196
+ @keyframes hilite2 {
197
+ 0% {
198
+ filter: brightness(1) saturate(1) drop-shadow(0 0 calc(var(--isize) * 0.1) var(--bgColor));
199
+ }
200
+ 35% {
201
+ filter: brightness(1) saturate(1) drop-shadow(0 0 calc(var(--isize) * 0.1) var(--bgColor));
202
+ }
203
+ 50% {
204
+ filter: brightness(1.05) saturate(1.05) drop-shadow(0 0 calc(var(--isize) * 0.05) var(--bgColor));
205
+ }
206
+ 65% {
207
+ filter: brightness(1) saturate(1) drop-shadow(0 0 calc(var(--isize) * 0.1) var(--bgColor));
208
+ }
209
+ 100% {
210
+ filter: brightness(1) saturate(1) drop-shadow(0 0 calc(var(--isize) * 0.1) var(--bgColor));
211
+ }
212
+ }
213
+
214
+ .Indicator.highlight::before {
215
+ border-width: 0;
216
+ }
217
+
218
+ .Indicator.highlight::after {
219
+ background: linear-gradient(45deg, transparent, transparent, rgba(255,255,255,0.75), transparent, transparent);
220
+ animation-name: hilite;
221
+ animation-timing-function: linear;
222
+ mix-blend-mode: luminosity;
223
+ border-width: 0;
224
+ filter: blur(calc(var(--isize) * 0.12));
225
+ background-size: 500% 120%;
226
+ }
227
+ @keyframes hilite {
228
+ 0% {
229
+ background-position: 100% center;
230
+ }
231
+ 100% {
232
+ background-position: 0 center;
233
+ }
234
+ }
235
+
236
+ /**
237
+ * Spin
238
+ */
239
+
240
+ .Indicator.spin {
241
+ background: transparent;
242
+ --mask: radial-gradient(circle, rgba(0, 0, 0, 0) 20%, rgba(0, 0, 0, 0) 45%, black 50%, black 100%);
243
+ -webkit-mask-image: var(--mask);
244
+ mask-image: var(--mask);
245
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.05) var(--bgColor));
246
+ }
247
+ .Indicator.spin::after,
248
+ .Indicator.spin::before {
249
+ animation-name: spin;
250
+ background: none;
251
+ animation-timing-function: linear;
252
+ mask-mode: luminance;
253
+ background-origin: border-box;
254
+ background-clip: border-box;
255
+ }
256
+ .Indicator.spin::before {
257
+ animation-timing-function:cubic-bezier(0.445, 0.05, 0.55, 0.95);
258
+ animation-delay: calc(-1 * var(--dur) * 0.85);
259
+ animation-name: spin2;
260
+ }
261
+ @keyframes spin {
262
+ 0% {
263
+ background-image: conic-gradient(from 0deg, transparent, transparent 73%, var(--bgColor) 73%, var(--bgColor) 100%);
264
+ transform: rotate(0deg) scale(1.2);
265
+ }
266
+ 100% {
267
+ background-image: conic-gradient(from 0deg, transparent, transparent 73%, var(--bgColor) 73%, var(--bgColor) 100%);
268
+ transform: rotate(360deg) scale(1.2);
269
+ }
270
+ }
271
+ @keyframes spin2 {
272
+ 0% {
273
+ background-image: conic-gradient(from 0deg, transparent, transparent 50%, var(--bgColor) 75%, var(--bgColor) 100%);
274
+ transform: rotate(0deg) scale(1.2);
275
+ }
276
+ 100% {
277
+ background-image: conic-gradient(from 0deg, transparent, transparent 50%, var(--bgColor) 75%, var(--bgColor) 100%);
278
+ transform: rotate(360deg) scale(1.2);
279
+ }
280
+ }
281
+
282
+ /**
283
+ * Pulse
284
+ */
285
+
286
+ .Indicator.pulse {
287
+ animation-name: pulse;
288
+ animation-timing-function: cubic-bezier(0.445, 0.05, 0.55, 0.95);
289
+ }
290
+ @keyframes pulse {
291
+ 0% {
292
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.03) var(--bgColor));
293
+ transform: scale(0.85);
294
+ }
295
+ 25% {
296
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.01) var(--bgColor)) brightness(1) saturate(1);
297
+ transform: scale(1);
298
+ }
299
+ 50% {
300
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.03) var(--bgColor)) brightness(1.25) saturate(1.25);
301
+ transform: scale(1);
302
+ }
303
+ 75% {
304
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.1) var(--bgColor)) brightness(1.25) saturate(1.25);
305
+ }
306
+ 100% {
307
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.03) var(--bgColor)) brightness(1) saturate(1);
308
+ transform: scale(0.85);
309
+ }
310
+ }
311
+ .Indicator.pulse::after {
312
+ border-color: var(--bgColor);
313
+ border-width: calc(var(--bwidth) * 0.75);
314
+ animation-name: task;
315
+ animation-timing-function: ease-out;
316
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.025) var(--bgColor));
317
+ }
318
+ @keyframes task {
319
+ 0% {
320
+ transform: scale(.5);
321
+ opacity: 1;
322
+ filter: blur(calc(var(--isize) * 0.05)) saturate(1);
323
+ }
324
+ 75% {
325
+ opacity: 0.25;
326
+ filter: blur(calc(var(--isize) * 0.01)) saturate(1);
327
+ }
328
+ 100% {
329
+ transform: scale(2.75);
330
+ opacity: 0;
331
+ filter: blur(0) saturate(0.5);
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Typing
337
+ */
338
+
339
+ .Indicator.typing {
340
+ --mask-image: radial-gradient(ellipse, black, black 65%, transparent 70%, transparent 100%);
341
+ mask-image: var(--mask-image);
342
+ -webkit-mask-image: var(--mask-image);
343
+ mask-size: 100% 100%;
344
+ width: calc(var(--isize) * 1.5);
345
+ background: transparent;
346
+ border-radius: 0;
347
+ position: relative;
348
+ }
349
+ .Indicator.typing::before,
350
+ .Indicator.typing::after {
351
+ animation-name: typing;
352
+ background: var(--bgColor);
353
+ width: calc(var(--isize) * 0.5);
354
+ height: calc(var(--isize) * 0.5);
355
+ top: 50%;
356
+ left: calc(50% - var(--isize) * 0.25);
357
+ animation-timing-function: ease-in;
358
+ animation-direction: reverse;
359
+ }
360
+ .Indicator.typing::before,
361
+ .Indicator.recording::before {
362
+ animation-delay: calc(-1 * var(--dur) * 0.5);
363
+ }
364
+ @keyframes typing {
365
+ 0% {
366
+ transform: translateX(-100%) translateY(-50%) scale(0.25);
367
+ opacity: 0;
368
+ }
369
+ 25% {
370
+ transform: translateX(-50%) translateY(-50%) scale(1);
371
+ opacity: 1;
372
+ }
373
+ 50% {
374
+ transform: translateX(0) translateY(-50%) scale(1);
375
+ filter: drop-shadow(0 0 calc(var(--isize) * 0.01) var(--bgColor));
376
+ }
377
+ 75% {
378
+ transform: translateX(50%) translateY(-50%) scale(1);
379
+ opacity: 1;
380
+ }
381
+ 100% {
382
+ transform: translateX(100%) translateY(-50%) scale(0.25);
383
+ opacity: 0;
384
+ }
385
+ }
386
+
387
+ </style>
@@ -0,0 +1,12 @@
1
+ import { type StatusColorOrString } from "../util/color.js";
2
+ type $$ComponentProps = {
3
+ /** The color of the tag. Select from default status colors or provide a CSS Color value. */
4
+ color?: StatusColorOrString;
5
+ /** Possible motion to apply to the indicator. */
6
+ motion?: "pulse" | "spin" | "blink" | "highlight" | "bulge" | "tunnel" | "typing";
7
+ /** ARIA label to use when a custom color is applied */
8
+ label?: string;
9
+ };
10
+ declare const Indicator: import("svelte").Component<$$ComponentProps, {}, "">;
11
+ type Indicator = ReturnType<typeof Indicator>;
12
+ export default Indicator;
@@ -0,0 +1,23 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+
4
+ let {
5
+ children,
6
+ }: {
7
+ children: Snippet;
8
+ } = $props();
9
+ </script>
10
+
11
+ <div class="Inset">
12
+ {@render children()}
13
+ </div>
14
+
15
+ <style>
16
+ .Inset {
17
+ margin-block: calc(var(--inset-block, 0) * -1);
18
+ margin-inline: calc(var(--inset-inline, 0) * -1);
19
+ }
20
+ .Inset > * {
21
+ display: block;
22
+ }
23
+ </style>
@@ -0,0 +1,7 @@
1
+ import type { Snippet } from "svelte";
2
+ type $$ComponentProps = {
3
+ children: Snippet;
4
+ };
5
+ declare const Inset: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type Inset = ReturnType<typeof Inset>;
7
+ export default Inset;
@@ -2,6 +2,7 @@
2
2
  import type { Snippet } from "svelte";
3
3
  import "../css/lutra.css";
4
4
  import Theme from "./Theme.svelte";
5
+ import OverlayContainer from "./OverlayContainer.svelte";
5
6
  /**
6
7
  * @description
7
8
  * Default layout component that imports default styles and wraps the entire application in a theme.
@@ -21,11 +22,11 @@
21
22
  <div class="Layout">
22
23
  {@render children()}
23
24
  </div>
25
+ <OverlayContainer />
24
26
  </Theme>
25
27
 
26
28
  <style>
27
29
  .Layout {
28
- background: var(--bg-app);
29
30
  min-height: 100dvh;
30
31
  height: 100dvh;
31
32
  }
@@ -0,0 +1,195 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import type { MenuItem as Item } from "./MenuTypes.js";
4
+ import MenuItem from "./MenuItem.svelte";
5
+ import UiContent from "./UIContent.svelte";
6
+ import { arrowNavigation, getNextFocusableElement, matchOnType } from "../util/keyboard.svelte.js";
7
+ import { findContainingBlock, getPossiblyContainedPosition } from "../util/dom.js";
8
+ import Overlay from "./Overlay.svelte";
9
+
10
+ /**
11
+ * @description
12
+ * A menu component that can be used to create dropdown menus.
13
+ */
14
+ let {
15
+ open = $bindable(false),
16
+ items,
17
+ trigger,
18
+ }: {
19
+ /** Whether the menu is open */
20
+ open?: boolean;
21
+ /** The items to display in the menu */
22
+ items: Item[];
23
+ /** The trigger for the menu */
24
+ trigger: string | Snippet<[{ toggle: () => void, isOpen: boolean }]>;
25
+ } = $props();
26
+
27
+ let _open = $state(open);
28
+ let triggerEl: HTMLDivElement | null = $state(null);
29
+ let contentEl: HTMLDivElement | null = $state(null);
30
+ let menuEl: HTMLDivElement | null = $state(null);
31
+ let currentIndex: number = $state(-1);
32
+ let keyboardHasFocus: boolean = $state(false);
33
+
34
+ const id = crypto.randomUUID();
35
+
36
+ $effect(() => {
37
+ if(_open) {
38
+ window.addEventListener('click', clickoutside);
39
+ window.addEventListener('keydown', onkeydown);
40
+ } else {
41
+ window.removeEventListener('click', clickoutside);
42
+ window.removeEventListener('keydown', onkeydown);
43
+ }
44
+ });
45
+
46
+ function toggle() {
47
+ _open = !_open;
48
+ }
49
+
50
+ let scrollable = $derived.by(() => {
51
+ if(!contentEl) return false;
52
+ return contentEl.scrollHeight > contentEl.clientHeight;
53
+ });
54
+
55
+ function onclick(e: MouseEvent) {
56
+ e.preventDefault();
57
+ _open = !_open;
58
+ }
59
+
60
+ function clickoutside(e: MouseEvent) {
61
+ if(!_open) return;
62
+ if(contentEl && !contentEl.contains(e.target as Node) && !triggerEl?.contains(e.target as Node)) {
63
+ _open = false;
64
+ }
65
+ }
66
+
67
+ function onkeydown(e: KeyboardEvent) {
68
+ if(!_open) return;
69
+ const active = document.activeElement as HTMLButtonElement | HTMLAnchorElement;
70
+ switch(e.key) {
71
+ case "Escape":
72
+ e.preventDefault();
73
+ _open = false;
74
+ break;
75
+ case "Tab":
76
+ // try to open the next menu if it exists
77
+ e.preventDefault();
78
+ e.stopPropagation();
79
+ _open = false;
80
+ setTimeout(() => {
81
+ const nextEl = getNextFocusableElement(menuEl, triggerEl, e.shiftKey ? "previous" : "next");
82
+ console.log('nextEl', nextEl)
83
+ if(nextEl) {
84
+ nextEl.focus();
85
+ if(nextEl.tagName === "BUTTON" || nextEl.tagName === "A" && nextEl.parentElement?.classList.contains("Trigger")) {
86
+ nextEl.click();
87
+ }
88
+ }
89
+ }, 0);
90
+ break;
91
+ case "ArrowDown":
92
+ e.preventDefault();
93
+ arrowNavigation(contentEl, "down");
94
+ matchOnType(contentEl, e); // call to reset the search
95
+ keyboardHasFocus = true;
96
+ break;
97
+ case "ArrowUp":
98
+ e.preventDefault();
99
+ arrowNavigation(contentEl, "up");
100
+ matchOnType(contentEl, e); // call to reset the search
101
+ keyboardHasFocus = true;
102
+ break;
103
+ case "Enter":
104
+ case "Space":
105
+ e.preventDefault();
106
+ active.click();
107
+ break;
108
+ default:
109
+ matchOnType(contentEl, e);
110
+ }
111
+ }
112
+
113
+ function mouseover(e: MouseEvent, item: Item, index: number) {
114
+ if(item.type === "item") {
115
+ currentIndex = index;
116
+ }
117
+ }
118
+
119
+ </script>
120
+
121
+
122
+ <UiContent>
123
+ <div class="MenuDropdown" bind:this={menuEl}>
124
+ <div
125
+ class="Trigger"
126
+ bind:this={triggerEl}
127
+ >
128
+ {#if typeof trigger === "string"}
129
+ <button type="button" class="button" {onclick} aria-haspopup="true" aria-controls={id} aria-expanded="{_open}">
130
+ {trigger}
131
+ </button>
132
+ {:else}
133
+ {@render trigger({ toggle: toggle, isOpen: _open })}
134
+ {/if}
135
+ </div>
136
+ {#if _open && triggerEl}
137
+ <Overlay position="anchor" id="o-{id}" anchor={triggerEl} layer="menu">
138
+ <div {id}
139
+ class="MenuDropdownContent"
140
+ class:scrollable={scrollable}
141
+ role="menu"
142
+ bind:this={contentEl}
143
+ >
144
+ <ul>
145
+ {#each items as item, index}
146
+ <MenuItem {keyboardHasFocus} onmouseover={mouseover} item={item} {index} />
147
+ {/each}
148
+ </ul>
149
+ </div>
150
+ </Overlay>
151
+ {/if}
152
+ </div>
153
+ </UiContent>
154
+
155
+ <style>
156
+ .MenuDropdown {
157
+ position: relative;
158
+ }
159
+
160
+ .Trigger {
161
+ position: relative;
162
+ display: inline-flex;
163
+ }
164
+
165
+ .MenuDropdownContent {
166
+ max-height: calc(50vh - 2rem);
167
+ margin: 0;
168
+ z-index: 1000;
169
+ margin: 0;
170
+ border: var(--menu-border-size) var(--menu-border-style) var(--menu-border-color);
171
+ border-radius: var(--menu-border-radius);
172
+ box-shadow: 0 0.5rem 1rem var(--shadow-color);
173
+ background-color: var(--menu-background-color);
174
+ width: var(--width, 25ch);
175
+ overflow-x: clip;
176
+ overflow-y: auto;
177
+ scrollbar-width: thin;
178
+ scrollbar-color: var(--scrollbar-color);
179
+ }
180
+
181
+ .MenuDropdownContent.scrollable {
182
+ border-top-right-radius: 0;
183
+ border-bottom-right-radius: 0;
184
+ }
185
+
186
+ .MenuDropdownContent :global(:has(li:last-of-type[data-type="item"])) {
187
+ padding-block-end: 0.5rem;
188
+ }
189
+
190
+ ul {
191
+ margin: 0;
192
+ list-style: none;
193
+ padding: 0;
194
+ }
195
+ </style>
@@ -0,0 +1,16 @@
1
+ import type { Snippet } from "svelte";
2
+ import type { MenuItem as Item } from "./MenuTypes.js";
3
+ type $$ComponentProps = {
4
+ /** Whether the menu is open */
5
+ open?: boolean;
6
+ /** The items to display in the menu */
7
+ items: Item[];
8
+ /** The trigger for the menu */
9
+ trigger: string | Snippet<[{
10
+ toggle: () => void;
11
+ isOpen: boolean;
12
+ }]>;
13
+ };
14
+ declare const MenuDropdown: import("svelte").Component<$$ComponentProps, {}, "open">;
15
+ type MenuDropdown = ReturnType<typeof MenuDropdown>;
16
+ export default MenuDropdown;