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,58 @@
1
+ <script lang="ts">
2
+ import Icon from '../components/Icon.svelte';
3
+ import { getContext, type Component, type Snippet } from 'svelte';
4
+ import type { LutraForm } from './types.js';
5
+
6
+ let {
7
+ href,
8
+ type = 'button',
9
+ class: className,
10
+ size = 'md',
11
+ kind = 'default',
12
+ disabled = false,
13
+ icon,
14
+ onclick,
15
+ children,
16
+ }: {
17
+ href?: string;
18
+ type?: 'button' | 'submit' | 'reset';
19
+ kind?: 'default' | 'outlined' | 'secondary' | 'warn';
20
+ size?: 'sm' | 'md' | 'lg' | 'xl';
21
+ class?: string;
22
+ disabled?: boolean;
23
+ icon?: string | Component;
24
+ onclick?: (event: MouseEvent) => void;
25
+ children: Snippet;
26
+ } = $props();
27
+
28
+ const form = getContext<LutraForm<any>>('form');
29
+ const align = getContext('form.actions.align');
30
+
31
+ </script>
32
+
33
+ {#if href}
34
+ <a class="Button Link button {size} {kind} {className} {align}" {href} {onclick}>
35
+ {#if icon}
36
+ <Icon {icon} />
37
+ {/if}
38
+ {@render children()}
39
+ </a>
40
+ {:else}
41
+ <button class="Button button {size} {kind} {className} {align}" type={type} disabled={disabled || form?.state === 'loading'} {onclick}>
42
+ {#if icon}
43
+ <Icon {icon} />
44
+ {/if}
45
+ {@render children()}
46
+ </button>
47
+ {/if}
48
+
49
+ <style>
50
+ .Button {
51
+ display: inline-flex;
52
+ align-items: center;
53
+ justify-content: center;
54
+ }
55
+ .Button.full {
56
+ width: 100%;
57
+ }
58
+ </style>
@@ -0,0 +1,15 @@
1
+ import { type Component, type Snippet } from 'svelte';
2
+ type $$ComponentProps = {
3
+ href?: string;
4
+ type?: 'button' | 'submit' | 'reset';
5
+ kind?: 'default' | 'outlined' | 'secondary' | 'warn';
6
+ size?: 'sm' | 'md' | 'lg' | 'xl';
7
+ class?: string;
8
+ disabled?: boolean;
9
+ icon?: string | Component;
10
+ onclick?: (event: MouseEvent) => void;
11
+ children: Snippet;
12
+ };
13
+ declare const Button: Component<$$ComponentProps, {}, "">;
14
+ type Button = ReturnType<typeof Button>;
15
+ export default Button;
@@ -0,0 +1,311 @@
1
+ <script lang="ts">
2
+ import TabbedContent from "../components/TabbedContent.svelte";
3
+ import TabbedContentItem from "../components/TabbedContentItem.svelte";
4
+ import UiContent from "../components/UIContent.svelte";
5
+ import { getLocaleFirstDayOfWeek } from "../util/locale.js";
6
+
7
+ let {
8
+ range
9
+ }: {
10
+ range?: {
11
+ min?: Date;
12
+ max?: Date;
13
+ }
14
+ } = $props();
15
+
16
+ const currentYear = new Date().getFullYear();
17
+ const startYear = Math.max(2020, currentYear - 5);
18
+ let years = $state(
19
+ Array.from(
20
+ { length: currentYear - startYear + 1 },
21
+ (_, i) => startYear + i
22
+ ).reverse()
23
+ );
24
+
25
+ let selectedStartYear = $state(currentYear);
26
+ let selectedEndYear = $state(currentYear);
27
+ let selectedStartMonth = $state(1);
28
+ let selectedEndMonth = $state(12);
29
+ let selectedStartDay = $state(1);
30
+ let selectedEndDay = $state(31);
31
+
32
+ let months = $state(
33
+ Array.from(
34
+ { length: 12 },
35
+ (_, i) => i + 1
36
+ )
37
+ );
38
+
39
+ const monthNames = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
40
+ const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
41
+ let monthName = $derived(monthNames[selectedStartMonth - 1]);
42
+
43
+ let localeFirstDay = $state(getLocaleFirstDayOfWeek());
44
+ let dayNamesOrdered = $derived([
45
+ ...dayNames.slice(localeFirstDay),
46
+ ...dayNames.slice(0, localeFirstDay)
47
+ ]);
48
+
49
+ function daysInMonth(year: number, month: number): number {
50
+ return new Date(year, month, 0).getDate();
51
+ }
52
+
53
+ let daysInCurrentMonth = $derived(daysInMonth(selectedStartYear, selectedStartMonth));
54
+ let firstWeekdayIndex = $derived(new Date(selectedStartYear, selectedStartMonth - 1, 1).getDay());
55
+ let firstWeekdayOffset = $derived((7 + firstWeekdayIndex - localeFirstDay) % 7);
56
+ let daysGrid = $derived((() => {
57
+ const totalCells = 42; // 6 rows x 7 days
58
+ const grid: Array<number | null> = [];
59
+ for (let i = 0; i < totalCells; i++) {
60
+ const dayNumber = i - firstWeekdayOffset + 1;
61
+ grid.push(dayNumber >= 1 && dayNumber <= daysInCurrentMonth ? dayNumber : null);
62
+ }
63
+ return grid;
64
+ })());
65
+
66
+ $effect(() => {
67
+ if (selectedStartDay > daysInCurrentMonth) {
68
+ selectedStartDay = daysInCurrentMonth;
69
+ }
70
+ if (selectedStartDay < 1) {
71
+ selectedStartDay = 1;
72
+ }
73
+ });
74
+
75
+ function pad2(n: number): string { return n < 10 ? `0${n}` : `${n}`; }
76
+ function toLocalDateTimeInputValue(date: Date): string {
77
+ const y = date.getFullYear();
78
+ const m = pad2(date.getMonth() + 1);
79
+ const d = pad2(date.getDate());
80
+ const hh = pad2(date.getHours());
81
+ const mm = pad2(date.getMinutes());
82
+ return `${y}-${m}-${d}T${hh}:${mm}`;
83
+ }
84
+
85
+ let startValue = $derived.by(() => {
86
+ return toLocalDateTimeInputValue(new Date(selectedStartYear, selectedStartMonth - 1, selectedStartDay));
87
+ });
88
+ let endValue = $derived.by(() => {
89
+ return toLocalDateTimeInputValue(new Date(selectedEndYear, selectedEndMonth - 1, selectedEndDay));
90
+ });
91
+
92
+ </script>
93
+
94
+ <UiContent>
95
+ <div class="Datepicker">
96
+ <div class="Fields">
97
+ <div class="Field Start">
98
+ <h6>From</h6>
99
+ <input type="datetime-local" bind:value={startValue} />
100
+ </div>
101
+ <div class="Field End">
102
+ <h6>To</h6>
103
+ <input type="datetime-local" bind:value={endValue} />
104
+ </div>
105
+ </div>
106
+ <div class="Components">
107
+ <div class="ComponentContainer YearComponent">
108
+ <div class="Component Years">
109
+ {#each years as year}
110
+ <button type="button" class="Year" class:selected={selectedStartYear === year} onclick={() => selectedStartYear = year}>{year}</button>
111
+ {/each}
112
+ </div>
113
+ </div>
114
+ <div class="ComponentContainer MonthComponent">
115
+ <div class="Component Months">
116
+ {#each months as month}
117
+ <button type="button" class="Month" class:selected={selectedStartMonth === month} onclick={() => selectedStartMonth = month}>
118
+ <span class="MonthName">{monthNames[month - 1]}</span>
119
+ <span class="MonthNumber">{month}</span>
120
+ </button>
121
+ {/each}
122
+ </div>
123
+ </div>
124
+ <div class="ComponentContainer DayComponent">
125
+ <div class="Component Days">
126
+ {#each dayNamesOrdered as name}
127
+ <div class="DayName">{name}</div>
128
+ {/each}
129
+ {#each daysGrid as cell}
130
+ {#if cell === null}
131
+ <div class="Day Empty"></div>
132
+ {:else}
133
+ <button type="button" class="Day" class:selected={selectedStartDay === cell} onmousedown={(e) => {
134
+ e.preventDefault();
135
+ selectedStartDay = cell;
136
+ }}>{cell}</button>
137
+ {/if}
138
+ {/each}
139
+ </div>
140
+ </div>
141
+ </div>
142
+ </div>
143
+ </UiContent>
144
+
145
+ <style>
146
+ button {
147
+ background-color: transparent;
148
+ border: none;
149
+ padding: 0;
150
+ cursor: pointer;
151
+ transition: all var(--transition-duration-fast) var(--transition-timing-function);
152
+ margin: 0;
153
+ }
154
+ .Datepicker {
155
+ width: fit-content;
156
+ display: grid;
157
+ grid-template-rows: 1fr auto;
158
+ border: var(--border-size-thin) solid var(--border-color);
159
+ border-radius: var(--border-radius-sm);
160
+ }
161
+ .Components, .Fields {
162
+ display: grid;
163
+ grid-template-columns: subgrid;
164
+ grid-column: span 3;
165
+ padding: var(--space-md);
166
+ }
167
+ .Components {
168
+ flex-direction: row;
169
+ justify-content: start;
170
+ align-items: start;
171
+ gap: var(--space-md);
172
+ user-select: none;
173
+ }
174
+ .YearComponent,
175
+ .MonthComponent {
176
+ grid-column: span 1;
177
+ }
178
+ .DayComponent {
179
+ grid-column: span 1;
180
+ }
181
+ .Years {
182
+ display: flex;
183
+ flex-direction: column;
184
+ justify-content: start;
185
+ align-items: start;
186
+ gap: var(--space-md);
187
+ }
188
+ .Year {
189
+ display: block;
190
+ font-size: 2em;
191
+ padding: var(--space-sm);
192
+ text-box: trim-both cap alphabetic;
193
+ }
194
+ .Months {
195
+ display: grid;
196
+ grid-template-columns: auto 1fr;
197
+ gap: var(--space-sm);
198
+ }
199
+ .Month {
200
+ display: grid;
201
+ grid-template-columns: subgrid;
202
+ grid-column: span 2;
203
+ line-height: 1;
204
+ padding: var(--space-sm);
205
+ text-align: right;
206
+ white-space: nowrap;
207
+ }
208
+ .MonthNumber {
209
+ font-size: 1.5em;
210
+ text-align: right;
211
+ text-box: trim-both cap alphabetic;
212
+ }
213
+
214
+ .MonthName {
215
+ font-size: 0.75em;
216
+ text-align: left;
217
+ text-box: trim-both cap alphabetic;
218
+ opacity: 0.5;
219
+ }
220
+
221
+ .Days {
222
+ display: grid;
223
+ grid-template-columns: repeat(7, 1fr);
224
+ flex-direction: row;
225
+ flex-wrap: wrap;
226
+ justify-content: start;
227
+ align-items: start;
228
+ gap: var(--space-sm);
229
+ }
230
+ .DayName {
231
+ display: flex;
232
+ text-box: trim-both cap alphabetic;
233
+ justify-content: center;
234
+ font-size: 0.75em;
235
+ opacity: 0.5;
236
+ align-items: center;
237
+ }
238
+ .Day {
239
+ display: flex;
240
+ text-box: trim-both cap alphabetic;
241
+ justify-content: center;
242
+ align-items: center;
243
+ border: var(--border-size-thin) solid var(--border-color);
244
+ border-radius: calc(var(--border-radius-sm) / 2);
245
+ padding: var(--space-050);
246
+ line-height: 1;
247
+ text-box: trim-both cap alphabetic;
248
+ }
249
+ .Day.Empty {
250
+ border-color: transparent;
251
+ }
252
+ .selected {
253
+ background-color: var(--background-selected);
254
+ border-radius: calc(var(--border-radius-sm) / 2);
255
+ }
256
+
257
+ .Fields {
258
+ display: grid;
259
+ grid-template-columns: 1fr 1fr;
260
+ border-bottom: var(--border-size-thin) solid var(--border-color);
261
+ padding: 0;
262
+ }
263
+ .Field {
264
+ display: grid;
265
+ grid-areas: "content";
266
+ border-right: var(--field-border-size) var(--field-border-style) var(--field-border-color);
267
+ align-items: center;
268
+ }
269
+ .Field:first-child { border-top-left-radius: var(--border-radius-sm); }
270
+ .Field:last-child { border-top-right-radius: var(--border-radius-sm); border-right: none; }
271
+ .Field h6 {
272
+ font-size: 0.85em;
273
+ font-weight: 400;
274
+ opacity: 0.5;
275
+ padding: var(--space-sm);
276
+ margin: 0;
277
+ height: 100%;
278
+ line-height: 1.5;
279
+ text-box: trim-both cap alphabetic;
280
+ }
281
+ .Field input {
282
+ font-family: var(--font-family-mono);
283
+ font-weight: 500;
284
+ font-size: 1.5em;
285
+ border: none;
286
+ padding: var(--space-md) var(--space-sm);
287
+ line-height: 1.5;
288
+ width: 100%;
289
+ text-box: trim-both cap alphabetic;
290
+ outline: none;
291
+ }
292
+ .Field input:focus-visible {
293
+ outline: none;
294
+ }
295
+ .Field:has(input:focus-visible) {
296
+ outline: var(--focus-ring);
297
+ border-color: var(--focus-ring-color);
298
+ }
299
+ /* Hide native datetime-local picker UI; we render our own date grid */
300
+ .Field input[type="datetime-local"] {
301
+ -webkit-appearance: textfield;
302
+ appearance: textfield;
303
+ }
304
+ .Field input[type="datetime-local"]::-webkit-calendar-picker-indicator {
305
+ display: none;
306
+ }
307
+ .Field input[type="datetime-local"]::-webkit-clear-button,
308
+ .Field input[type="datetime-local"]::-webkit-inner-spin-button {
309
+ display: none;
310
+ }
311
+ </style>
@@ -0,0 +1,9 @@
1
+ type $$ComponentProps = {
2
+ range?: {
3
+ min?: Date;
4
+ max?: Date;
5
+ };
6
+ };
7
+ declare const Datepicker: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type Datepicker = ReturnType<typeof Datepicker>;
9
+ export default Datepicker;
@@ -0,0 +1,178 @@
1
+ <script lang="ts">
2
+ import { getContext, type Snippet } from "svelte";
3
+ import Label from "./Label.svelte";
4
+ import FieldError from "./FieldError.svelte";
5
+ import type { FormField, FormIssue } from "./types.js";
6
+ import StringOrSnippet from "../util/StringOrSnippet.svelte";
7
+
8
+ /**
9
+ * @description
10
+ * This component is used internally by the Input, Select, etc. components to render the label and the input field in a consistent way.
11
+ */
12
+ let {
13
+ id,
14
+ contained,
15
+ prefix,
16
+ suffix,
17
+ label,
18
+ labelTip,
19
+ labelHelp,
20
+ help,
21
+ type = "text",
22
+ direction = 'column', // 'row' | 'column'
23
+ required,
24
+ children,
25
+ field,
26
+ issue,
27
+ }: {
28
+ id: string;
29
+ contained?: boolean;
30
+ prefix?: string | Snippet;
31
+ suffix?: string | Snippet;
32
+ help?: string | Snippet;
33
+ type?: string;
34
+ label?: string | Snippet;
35
+ labelTip?: string | Snippet;
36
+ labelHelp?: string | Snippet;
37
+ direction?: 'row' | 'column';
38
+ required?: boolean;
39
+ children: Snippet;
40
+ field?: FormField;
41
+ issue?: FormIssue;
42
+ } = $props();
43
+
44
+ if(contained === undefined) { contained = getContext('lutra.form.contained') ?? getContext('lutra.contained') ?? false; }
45
+ </script>
46
+
47
+ {#snippet _prefix()}
48
+ <div class="Fix Prefix">
49
+ <StringOrSnippet content={prefix} />
50
+ </div>
51
+ {/snippet}
52
+
53
+ {#snippet _suffix()}
54
+ <div class="Fix Suffix">
55
+ <StringOrSnippet content={suffix} />
56
+ </div>
57
+ {/snippet}
58
+
59
+ <div class="FieldContentContainer">
60
+ <div class="FieldContent {type} {direction}" class:contained>
61
+ <Label {label} tip={labelTip} help={labelHelp} {id} {required} />
62
+ {#if contained}
63
+ <div
64
+ class="Field"
65
+ class:hasPrefix={prefix}
66
+ class:hasSuffix={suffix}
67
+ class:invalid={field?.tainted && issue?.code}
68
+ >
69
+ {#if prefix}
70
+ {@render _prefix()}
71
+ {/if}
72
+
73
+ {@render children()}
74
+
75
+ {#if suffix}
76
+ {@render _suffix()}
77
+ {/if}
78
+ </div>
79
+ {:else}
80
+ {#if prefix}
81
+ {@render _prefix()}
82
+ {/if}
83
+
84
+ {@render children()}
85
+
86
+ {#if suffix}
87
+ {@render _suffix()}
88
+ {/if}
89
+ {/if}
90
+
91
+ </div>
92
+
93
+ {#if help}
94
+ <div class="Help">
95
+ <StringOrSnippet content={help} />
96
+ </div>
97
+ {/if}
98
+
99
+ {#if field?.tainted && issue?.code}
100
+ <FieldError code={issue.code} message={issue.message} />
101
+ {/if}
102
+ </div>
103
+
104
+ <style>
105
+ .FieldContentContainer {
106
+ display: flex;
107
+ flex-direction: column;
108
+ gap: var(--form-field-gap, var(--space-sm));
109
+ }
110
+ .FieldContent {
111
+ display: flex;
112
+ gap: var(--form-label-gap, var(--space-sm));
113
+ flex-direction: column;
114
+ font-size: 1em;
115
+ }
116
+ .FieldContent.row {
117
+ flex-direction: row-reverse;
118
+ justify-content: start;
119
+ align-items: center;
120
+ flex-shrink: 0;
121
+ }
122
+ .Field {
123
+ background: var(--field-background, transparent);
124
+ border: var(--field-border-size) var(--field-border-style) var(--field-border-color);
125
+ border-radius: var(--field-border-radius);
126
+ display: flex;
127
+ }
128
+ .Field.invalid {
129
+ border-color: var(--field-border-color-invalid);
130
+ }
131
+ .Field > *:not(input) {
132
+ flex-grow: 0;
133
+ flex-shrink: 0;
134
+ }
135
+ .Fix {
136
+ display: flex;
137
+ align-items: center;
138
+ padding-inline: var(--form-field-gap, var(--space-md));
139
+ font-size: 1em;
140
+ text-box: trim-both cap alphabetic;
141
+ color: var(--text-subtle);
142
+ }
143
+ .Suffix {
144
+ padding-inline-start: 0;
145
+ }
146
+ .Prefix {
147
+ padding-inline-end: 0;
148
+ }
149
+ .Field:has(:global(input:focus-visible)),
150
+ .Field:has(:global(select:focus-visible)),
151
+ .Field:has(:global(textarea:focus-visible)) {
152
+ outline: var(--focus-ring);
153
+ border-color: var(--focus-ring-color);
154
+ }
155
+ .Field:has(:global(input:focus-visible:user-invalid)),
156
+ .Field:has(:global(select:focus-visible:user-invalid)),
157
+ .Field:has(:global(textarea:focus-visible:user-invalid)) {
158
+ outline-color: var(--focus-ring-color-invalid);
159
+ border-color: var(--focus-ring-color-invalid);
160
+ }
161
+ .Field.hasPrefix :global(input) {
162
+ padding-inline-start: var(--space-025);
163
+ }
164
+ .Field :global(button) {
165
+ margin-right: var(--space-025);
166
+ }
167
+ .Field :global(button:focus-visible) {
168
+ outline: var(--focus-outline);
169
+ outline-offset: 3px;
170
+ border-radius: calc(var(--field-radius) - 2px);
171
+ }
172
+ .Help {
173
+ font-size: min(11px, var(--font-size-075));
174
+ line-height: var(--font-line-height-tight);
175
+ color: var(--text-color-p-subtle);
176
+ font-weight: var(--font-weight-normal);
177
+ }
178
+ </style>
@@ -0,0 +1,21 @@
1
+ import { type Snippet } from "svelte";
2
+ import type { FormField, FormIssue } from "./types.js";
3
+ type $$ComponentProps = {
4
+ id: string;
5
+ contained?: boolean;
6
+ prefix?: string | Snippet;
7
+ suffix?: string | Snippet;
8
+ help?: string | Snippet;
9
+ type?: string;
10
+ label?: string | Snippet;
11
+ labelTip?: string | Snippet;
12
+ labelHelp?: string | Snippet;
13
+ direction?: 'row' | 'column';
14
+ required?: boolean;
15
+ children: Snippet;
16
+ field?: FormField;
17
+ issue?: FormIssue;
18
+ };
19
+ declare const FieldContent: import("svelte").Component<$$ComponentProps, {}, "">;
20
+ type FieldContent = ReturnType<typeof FieldContent>;
21
+ export default FieldContent;
@@ -0,0 +1,24 @@
1
+ <script lang="ts">
2
+ /**
3
+ * @description
4
+ * This component is used internally by the Input, Select, etc. components to render the error message.
5
+ */
6
+ let {
7
+ code,
8
+ message
9
+ }: {
10
+ code: string;
11
+ message?: string;
12
+ } = $props();
13
+ </script>
14
+
15
+ <p class="Error">{message ? message : code}</p>
16
+
17
+ <style>
18
+ p.Error {
19
+ color: var(--field-color-danger, light-dark(red, red));
20
+ font-size: max(0.85em, 11px);
21
+ font-weight: var(--font-weight-normal);
22
+ text-wrap: balance;
23
+ }
24
+ </style>
@@ -0,0 +1,7 @@
1
+ type $$ComponentProps = {
2
+ code: string;
3
+ message?: string;
4
+ };
5
+ declare const FieldError: import("svelte").Component<$$ComponentProps, {}, "">;
6
+ type FieldError = ReturnType<typeof FieldError>;
7
+ export default FieldError;