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,103 @@
1
+ <script lang="ts">
2
+ import { getContext, type Snippet } from "svelte";
3
+
4
+ /**
5
+ * @description
6
+ * A fieldset is a group of related form elements that can be disabled or enabled.
7
+ * @example
8
+ * <script>
9
+ * import Input from "lutra/Input.svelte";
10
+ * <\/script>
11
+ * <Fieldset legend="Personal Information">
12
+ * <Input label="First Name" />
13
+ * <Input label="Last Name" />
14
+ * </Fieldset>
15
+ */
16
+
17
+ let {
18
+ fullWidth,
19
+ description,
20
+ contained,
21
+ rounded,
22
+ legend,
23
+ columns = 1,
24
+ children,
25
+ }: {
26
+ /** Whether the fieldset should be full width. */
27
+ fullWidth?: boolean;
28
+ /** The description of the fieldset. */
29
+ description?: string | Snippet;
30
+ /** Whether the fieldset should be contained. */
31
+ contained?: boolean;
32
+ /** Whether the fieldset should be rounded. */
33
+ rounded?: boolean;
34
+ /** The legend of the fieldset. */
35
+ legend?: string | Snippet;
36
+ /** The number of columns to display the fieldset in. You can pass a single number or an array of numbers. The array will be used to set the number of columns at different breakpoints (lg, md, sm). */
37
+ columns?: number | [number] | [number, number] | [number, number, number];
38
+ /** Content to be rendered inside the fieldset. */
39
+ children: Snippet;
40
+ } = $props();
41
+
42
+ if(contained === undefined) { contained = getContext('lutra.form.contained') ?? getContext('lutra.contained') ?? false; }
43
+
44
+ let columnsArray = Array.isArray(columns) ? columns : [columns, columns, columns];
45
+ let lgColumns = columnsArray[0];
46
+ let mdColumns = columnsArray[1] || lgColumns;
47
+ let smColumns = columnsArray[2] || mdColumns;
48
+
49
+ </script>
50
+
51
+ <div class="FieldsetContainer">
52
+ <fieldset class:fullWidth class:rounded class:contained class="cols-{columns}" style="--lg-cols: {lgColumns}; --md-cols: {mdColumns}; --sm-cols: {smColumns}">
53
+ {#if legend}
54
+ <legend>
55
+ {#if typeof legend === 'string'}
56
+ {legend}
57
+ {:else}
58
+ {@render legend()}
59
+ {/if}
60
+ </legend>
61
+ {/if}
62
+ {#if description}
63
+ {#if typeof description === 'string'}
64
+ <small>{description}</small>
65
+ {:else}
66
+ {@render description()}
67
+ {/if}
68
+ {/if}
69
+ {@render children()}
70
+ </fieldset>
71
+ </div>
72
+
73
+ <style>
74
+ .FieldsetContainer {
75
+ container-type: inline-size;
76
+ }
77
+ fieldset {
78
+ display: grid;
79
+ width: var(--width, fit-content);
80
+ grid-template-columns: repeat(var(--lg-cols), 1fr);
81
+ gap: var(--gap, 1.5em);
82
+ }
83
+ legend {
84
+ font-weight: 500;
85
+ font-size: var(--font-size, 1em);
86
+ }
87
+ fieldset.contained {
88
+ padding: 1.5em;
89
+ }
90
+ fieldset.fullWidth {
91
+ width: 100%;
92
+ }
93
+ @container(max-width: 960px) {
94
+ fieldset {
95
+ grid-template-columns: repeat(var(--md-cols), 1fr);
96
+ }
97
+ }
98
+ @container(max-width: 640px) {
99
+ fieldset {
100
+ grid-template-columns: repeat(var(--sm-cols), 1fr);
101
+ }
102
+ }
103
+ </style>
@@ -0,0 +1,20 @@
1
+ import { type Snippet } from "svelte";
2
+ type $$ComponentProps = {
3
+ /** Whether the fieldset should be full width. */
4
+ fullWidth?: boolean;
5
+ /** The description of the fieldset. */
6
+ description?: string | Snippet;
7
+ /** Whether the fieldset should be contained. */
8
+ contained?: boolean;
9
+ /** Whether the fieldset should be rounded. */
10
+ rounded?: boolean;
11
+ /** The legend of the fieldset. */
12
+ legend?: string | Snippet;
13
+ /** The number of columns to display the fieldset in. You can pass a single number or an array of numbers. The array will be used to set the number of columns at different breakpoints (lg, md, sm). */
14
+ columns?: number | [number] | [number, number] | [number, number, number];
15
+ /** Content to be rendered inside the fieldset. */
16
+ children: Snippet;
17
+ };
18
+ declare const Fieldset: import("svelte").Component<$$ComponentProps, {}, "">;
19
+ type Fieldset = ReturnType<typeof Fieldset>;
20
+ export default Fieldset;
@@ -0,0 +1,220 @@
1
+ <script lang="ts">
2
+ import { enhance } from "$app/forms";
3
+ import { goto } from '$app/navigation';
4
+ import UiContent from "../components/UIContent.svelte";
5
+ import { Bodyguard, type BodyguardValidator } from "@auth70/bodyguard";
6
+ import type { ActionResult } from "@sveltejs/kit";
7
+ import { onMount, setContext } from "svelte";
8
+ import type { Snippet } from "svelte";
9
+ import { dezerialize } from "zodex";
10
+ import type { AddBeforeSubmitFn, BeforeSubmitFn, LutraForm } from "./types.js";
11
+ import { type ZodObject } from "zod";
12
+ import { getIndividualValidators, parseFormIssues } from "./form.js";
13
+ import { useForm } from "./client.svelte.js";
14
+
15
+ let {
16
+ name = 'form',
17
+ form: _form,
18
+ formEl = $bindable(null),
19
+ action,
20
+ enctype = "multipart/form-data",
21
+ method = "POST",
22
+ beforesubmit,
23
+ fullWidth = false,
24
+ onresult,
25
+ resetOnUpdate,
26
+ children,
27
+ contained,
28
+ spacing,
29
+ actionGap,
30
+ fieldGap,
31
+ labelGap,
32
+ titleGap,
33
+ sectionGap,
34
+ padding,
35
+ paddingBlock,
36
+ paddingInline,
37
+ }: {
38
+ name?: string;
39
+ form?: LutraForm<any>;
40
+ formEl?: HTMLFormElement | null;
41
+ action?: string;
42
+ enctype?: "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain";
43
+ method?: "GET" | "POST";
44
+ beforesubmit?: BeforeSubmitFn;
45
+ fullWidth?: boolean;
46
+ resetOnUpdate?: boolean;
47
+ onresult?: (args: {
48
+ formData: FormData;
49
+ formElement: HTMLFormElement;
50
+ action: URL;
51
+ result: ActionResult<Record<string, unknown> | undefined, Record<string, unknown> | undefined>;
52
+ update: (options?: {
53
+ reset?: boolean | undefined;
54
+ invalidateAll?: boolean | undefined;
55
+ } | undefined) => void;
56
+ }) => void;
57
+ children: Snippet;
58
+ contained?: boolean;
59
+ spacing?: string;
60
+ actionGap?: string;
61
+ fieldGap?: string;
62
+ labelGap?: string;
63
+ titleGap?: string;
64
+ sectionGap?: string;
65
+ padding?: string;
66
+ paddingBlock?: string;
67
+ paddingInline?: string;
68
+ } = $props();
69
+
70
+ let form = $state(_form ? useForm(_form) : null);
71
+
72
+ setContext('form', form);
73
+ setContext('form.validators', form ? getIndividualValidators(form) : null);
74
+
75
+ const beforesubmitFunctions: { id: string, fn: BeforeSubmitFn }[] = [];
76
+ const addBeforeSubmit: AddBeforeSubmitFn = (id, fn) => {
77
+ if(beforesubmitFunctions.find((f) => f.id === id)) {
78
+ return;
79
+ }
80
+ beforesubmitFunctions.push({ id, fn });
81
+ }
82
+ setContext('form.beforesubmit', addBeforeSubmit);
83
+
84
+ const schema = form?.schema ? dezerialize(form?.schema) as ZodObject<any> : null;
85
+ const bodyguard = new Bodyguard();
86
+
87
+ function setFormIssuesAndFields(issues: any, fields: any) {
88
+ console.log('setFormIssuesAndFields', issues, fields)
89
+ if(form) {
90
+ form.issues = issues;
91
+ form.fields = fields;
92
+ }
93
+ }
94
+
95
+ async function validate() {
96
+ if(form && schema) {
97
+ form.tainted = true;
98
+ const req = new Request('localhost', {
99
+ method: 'POST',
100
+ body: new FormData(formEl!),
101
+ });
102
+ const result = await bodyguard.softForm(req, schema.parse as BodyguardValidator);
103
+ if(result.success === true) {
104
+ form.valid = true;
105
+ form.issues = [];
106
+ } else {
107
+ form.valid = false;
108
+ form.issues = parseFormIssues((result.error as any).issues);
109
+ }
110
+ }
111
+ }
112
+
113
+ onMount(() => {
114
+ validate();
115
+ });
116
+
117
+ const styleParams = {
118
+ actionGap,
119
+ fieldGap,
120
+ labelGap,
121
+ titleGap,
122
+ sectionGap,
123
+ padding,
124
+ paddingBlock,
125
+ paddingInline,
126
+ };
127
+
128
+ const style = `
129
+ ${actionGap ? `--form-action-gap: ${actionGap};` : ''}
130
+ ${fieldGap ? `--form-field-gap: ${fieldGap};` : ''}
131
+ ${labelGap ? `--form-label-gap: ${labelGap};` : ''}
132
+ ${titleGap ? `--form-title-gap: ${titleGap};` : ''}
133
+ ${sectionGap ? `--form-section-gap: ${sectionGap};` : ''}
134
+ ${
135
+ paddingBlock || paddingInline
136
+ ? `
137
+ ${paddingBlock ? `--form-padding-block: ${paddingBlock};` : ''}
138
+ ${paddingInline ? `--form-padding-inline: ${paddingInline};` : ''}
139
+ `
140
+ : padding
141
+ ? `--form-padding-block: ${padding}; --form-padding-inline: ${padding};`
142
+ : ''
143
+ }
144
+ `;
145
+
146
+ </script>
147
+
148
+ <UiContent>
149
+ <form
150
+ {method}
151
+ {action}
152
+ {enctype}
153
+ style={style}
154
+ bind:this={formEl}
155
+ onchange={validate}
156
+ use:enhance={async ({ formElement, formData, action, cancel, submitter }) => {
157
+ // `formElement` is this `<form>` element
158
+ // `formData` is its `FormData` object that's about to be submitted
159
+ // `action` is the URL to which the form is posted
160
+ // calling `cancel()` will prevent the submission
161
+ // `submitter` is the `HTMLElement` that caused the form to be submitted
162
+ if(form) form.state = 'loading';
163
+ //await Promise.resolve(beforesubmit(form));
164
+ if(beforesubmit) await Promise.resolve(beforesubmit({ form: formElement, data: formData, cancel: () => {
165
+ if(form) form.state = 'error';
166
+ cancel();
167
+ }}));
168
+ for(const { id, fn } of beforesubmitFunctions) {
169
+ await Promise.resolve(fn({ form: formElement, data: formData, cancel: () => {
170
+ if(form) form.state = 'error';
171
+ cancel();
172
+ }}));
173
+ }
174
+ console.log('form state', form?.state);
175
+ return async (opts) => {
176
+ const { result, update } = opts;
177
+ // `result` is an `ActionResult` object
178
+ // `update` is a function which triggers the default logic that would be triggered if this callback wasn't set
179
+ console.log('form result', opts);
180
+ if(onresult) {
181
+ console.log('calling onresult', opts);
182
+ onresult(opts);
183
+ }
184
+ const resultForm = result.type !== "redirect" && result.type !== "error" ? result?.data![name] : null;
185
+ console.log('resultForm', resultForm, form);
186
+ if(result.type === "success") {
187
+ if(resultForm && form) {
188
+ form.valid = Object.assign({ valid: false }, resultForm).valid ?? false;
189
+ }
190
+ if(form) form.state = 'success';
191
+ console.log('form state', form?.state, resetOnUpdate, opts);
192
+ update({ reset: !!resetOnUpdate });
193
+ } else if(result.type === "failure") {
194
+ console.log('FAILURE', opts);
195
+ if(resultForm && form) {
196
+ console.log('setting form issues and fields')
197
+ setFormIssuesAndFields(
198
+ Object.assign({ issues: [] }, resultForm).issues, // Have to assign to avoid type error as we cant use `as` here
199
+ Object.assign({ fields: [] }, resultForm).fields,
200
+ );
201
+ }
202
+ if(form) form.state = 'error';
203
+ } else if(result.type === "error") {
204
+ console.error('[lutra] Error from form enhance call', result.error, opts);
205
+ if(form) form.state = 'error';
206
+ } else if(result.type === "redirect") {
207
+ console.log('redirect', opts);
208
+ //window.location.href = result.location;
209
+ if(form) form.state = 'success';
210
+ await goto(result.location);
211
+ }
212
+ };
213
+ }}
214
+ >
215
+ {@render children()}
216
+ </form>
217
+ </UiContent>
218
+
219
+ <style>
220
+ </style>
@@ -0,0 +1,38 @@
1
+ import type { ActionResult } from "@sveltejs/kit";
2
+ import type { Snippet } from "svelte";
3
+ import type { BeforeSubmitFn, LutraForm } from "./types.js";
4
+ type $$ComponentProps = {
5
+ name?: string;
6
+ form?: LutraForm<any>;
7
+ formEl?: HTMLFormElement | null;
8
+ action?: string;
9
+ enctype?: "application/x-www-form-urlencoded" | "multipart/form-data" | "text/plain";
10
+ method?: "GET" | "POST";
11
+ beforesubmit?: BeforeSubmitFn;
12
+ fullWidth?: boolean;
13
+ resetOnUpdate?: boolean;
14
+ onresult?: (args: {
15
+ formData: FormData;
16
+ formElement: HTMLFormElement;
17
+ action: URL;
18
+ result: ActionResult<Record<string, unknown> | undefined, Record<string, unknown> | undefined>;
19
+ update: (options?: {
20
+ reset?: boolean | undefined;
21
+ invalidateAll?: boolean | undefined;
22
+ } | undefined) => void;
23
+ }) => void;
24
+ children: Snippet;
25
+ contained?: boolean;
26
+ spacing?: string;
27
+ actionGap?: string;
28
+ fieldGap?: string;
29
+ labelGap?: string;
30
+ titleGap?: string;
31
+ sectionGap?: string;
32
+ padding?: string;
33
+ paddingBlock?: string;
34
+ paddingInline?: string;
35
+ };
36
+ declare const Form: import("svelte").Component<$$ComponentProps, {}, "formEl">;
37
+ type Form = ReturnType<typeof Form>;
38
+ export default Form;
@@ -0,0 +1,80 @@
1
+ <script lang="ts">
2
+ import StringOrSnippet from "../util/StringOrSnippet.svelte";
3
+ import { setContext, type Snippet } from "svelte";
4
+
5
+ let {
6
+ align = 'end',
7
+ children,
8
+ info,
9
+ }: {
10
+ align?: 'justified' | 'start' | 'center' | 'end' | 'full';
11
+ children: Snippet;
12
+ info?: string | Snippet;
13
+ } = $props();
14
+
15
+ setContext('form.actions.align', align);
16
+ </script>
17
+
18
+ <div class="FormActions {align}" class:hasInfo={!!info}>
19
+ {#if info}
20
+ <div class="Info">
21
+ <StringOrSnippet content={info} />
22
+ </div>
23
+ {/if}
24
+ <div class="Actions">
25
+ {@render children()}
26
+ </div>
27
+ </div>
28
+
29
+ <style>
30
+ .FormActions {
31
+ display: grid;
32
+ background: color-mix(in srgb, var(--form-background-actions) calc(var(--fcc) * 100%), transparent);
33
+ padding: calc(var(--space-md) * var(--fcc)) calc(var(--space-lg) * var(--fcc));
34
+ grid-column: 1 / -1;
35
+ grid-template-columns: subgrid;
36
+ border-bottom-left-radius: inherit;
37
+ border-bottom-right-radius: inherit;
38
+ align-items: center;
39
+ }
40
+ .Actions {
41
+ display: flex;
42
+ flex-direction: row;
43
+ align-items: center;
44
+ justify-content: space-between;
45
+ gap: var(--space-md);
46
+ grid-column: 2 / -1;
47
+ }
48
+ .FormActions.start .Actions {
49
+ justify-content: start;
50
+ grid-column: 1 / -1;
51
+ }
52
+ .FormActions.center .Actions {
53
+ justify-content: center;
54
+ grid-column: 1 / -1;
55
+ }
56
+ .FormActions.end .Actions {
57
+ justify-content: end;
58
+ grid-column: 1 / -1;
59
+ }
60
+ .FormActions.hasInfo .Actions {
61
+ grid-column: 2 / -1;
62
+ }
63
+ .FormActions.full .Actions {
64
+ grid-column: 1 / -1;
65
+ }
66
+ @container (max-width: 480px) {
67
+ .FormActions {
68
+ justify-content: center;
69
+ gap: var(--space-sm);
70
+ }
71
+ .Info {
72
+ order: 1;
73
+ text-align: center;
74
+ }
75
+ .Actions {
76
+ order: -1;
77
+ flex-direction: column;
78
+ }
79
+ }
80
+ </style>
@@ -0,0 +1,9 @@
1
+ import { type Snippet } from "svelte";
2
+ type $$ComponentProps = {
3
+ align?: 'justified' | 'start' | 'center' | 'end' | 'full';
4
+ children: Snippet;
5
+ info?: string | Snippet;
6
+ };
7
+ declare const FormActions: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type FormActions = ReturnType<typeof FormActions>;
9
+ export default FormActions;
@@ -0,0 +1,96 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+
4
+ /**
5
+ * @description
6
+ * A field section is a group of related form elements.
7
+ */
8
+ let {
9
+ title,
10
+ description,
11
+ children,
12
+ }: {
13
+ title?: string | Snippet;
14
+ description?: string | Snippet;
15
+ children: Snippet;
16
+ } = $props();
17
+ </script>
18
+
19
+ <div class="FormSection" class:noTitle={!title && !description}>
20
+ {#if title || description}
21
+ <div class="FormSectionTitle">
22
+ {#if title}
23
+ {#if typeof title === "string"}
24
+ <h5>{title}</h5>
25
+ {:else}
26
+ {@render title()}
27
+ {/if}
28
+ {/if}
29
+ {#if description}
30
+ {#if typeof description === "string"}
31
+ <small>{description}</small>
32
+ {:else}
33
+ {@render description()}
34
+ {/if}
35
+ {/if}
36
+ </div>
37
+ {/if}
38
+ <div class="FormSectionContent">
39
+ {@render children()}
40
+ </div>
41
+ </div>
42
+
43
+ <style>
44
+ .FormSection {
45
+ padding: 0;
46
+ display: grid;
47
+ grid-template-columns: subgrid;
48
+ grid-column: 1 / -1;
49
+ padding-block: calc(var(--fcc) * var(--form-padding-block));
50
+ padding-inline: calc(var(--fcc) * var(--form-padding-inline));
51
+ gap: var(--form-section-gap, var(--space-lg));
52
+ }
53
+ .FormSectionTitle {
54
+ display: flex;
55
+ flex-direction: column;
56
+ background-color: var(--base);
57
+ gap: var(--form-title-gap, var(--space-md));
58
+ text-wrap: balance;
59
+ }
60
+ .FormSection:not(:first-child) {
61
+ border-top: calc(var(--fcc) * var(--border-size)) var(--border-style) var(--border-color);
62
+ }
63
+ .FormSectionContent {
64
+ display: grid;
65
+ gap: var(--form-field-gap, var(--space-lg));
66
+ }
67
+ .FormSection.noTitle .FormSectionContent {
68
+ grid-column: 1 / -1;
69
+ }
70
+ @media(max-width: 1280px) {
71
+ .FormSection {
72
+ padding: var(--space-lg);
73
+ gap: var(--space-md);
74
+ }
75
+ .FormSectionTitle {
76
+ gap: var(--space-sm);
77
+ padding-block-end: var(--space-md);
78
+ }
79
+ }
80
+ @media(max-width: 640px) {
81
+ .FormSection {
82
+ padding: var(--space-md);
83
+ gap: var(--space-md);
84
+ }
85
+ .FormSectionTitle {
86
+ gap: var(--space-sm);
87
+ padding-block-end: var(--space-md);
88
+ }
89
+ }
90
+ @container (max-width: 800px) {
91
+ .FormSectionTitle,
92
+ .FormSectionContent {
93
+ grid-column: 1 / -1;
94
+ }
95
+ }
96
+ </style>
@@ -0,0 +1,9 @@
1
+ import type { Snippet } from "svelte";
2
+ type $$ComponentProps = {
3
+ title?: string | Snippet;
4
+ description?: string | Snippet;
5
+ children: Snippet;
6
+ };
7
+ declare const FormSection: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type FormSection = ReturnType<typeof FormSection>;
9
+ export default FormSection;