@finsweet/webflow-apps-utils 1.0.3 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/router/Router.mdx +958 -0
  4. package/dist/router/Router.stories.d.ts +6 -0
  5. package/dist/router/Router.stories.js +47 -0
  6. package/dist/router/examples/RouterExample.svelte +271 -0
  7. package/dist/router/examples/RouterExample.svelte.d.ts +18 -0
  8. package/dist/router/examples/index.d.ts +4 -0
  9. package/dist/router/examples/index.js +4 -0
  10. package/dist/router/examples/pages/AboutPage.svelte +568 -0
  11. package/dist/router/examples/pages/AboutPage.svelte.d.ts +13 -0
  12. package/dist/router/examples/pages/HomePage.svelte +200 -0
  13. package/dist/router/examples/pages/HomePage.svelte.d.ts +14 -0
  14. package/dist/router/examples/pages/NotFoundPage.svelte +307 -0
  15. package/dist/router/examples/pages/NotFoundPage.svelte.d.ts +17 -0
  16. package/dist/router/hooks.svelte.d.ts +2 -2
  17. package/dist/router/index.d.ts +3 -0
  18. package/dist/router/index.js +3 -0
  19. package/dist/router/{Link.svelte → providers/Link.svelte} +1 -1
  20. package/dist/router/{Route.svelte → providers/Route.svelte} +1 -1
  21. package/dist/router/{Route.svelte.d.ts → providers/Route.svelte.d.ts} +1 -1
  22. package/dist/router/{Router.svelte → providers/RouterProvider.svelte} +22 -5
  23. package/dist/router/{Router.svelte.d.ts → providers/RouterProvider.svelte.d.ts} +8 -4
  24. package/dist/router/providers/index.d.ts +3 -0
  25. package/dist/router/providers/index.js +3 -0
  26. package/dist/router/{index.svelte.d.ts → router.svelte.d.ts} +1 -3
  27. package/dist/router/{index.svelte.js → router.svelte.js} +1 -4
  28. package/dist/stores/docs/Form.mdx +542 -0
  29. package/dist/stores/forms.d.ts +41 -4
  30. package/dist/stores/forms.js +86 -32
  31. package/dist/types/customCode.d.ts +1 -1
  32. package/dist/types/window.d.ts +1 -0
  33. package/dist/ui/components/copy-text/CopyText.stories.d.ts +70 -0
  34. package/dist/ui/components/copy-text/CopyText.stories.js +241 -0
  35. package/dist/ui/components/copy-text/CopyText.svelte +249 -0
  36. package/dist/ui/components/copy-text/CopyText.svelte.d.ts +4 -0
  37. package/dist/ui/components/copy-text/index.d.ts +2 -0
  38. package/dist/ui/components/copy-text/index.js +1 -0
  39. package/dist/ui/components/copy-text/types.d.ts +52 -0
  40. package/dist/ui/components/copy-text/types.js +1 -0
  41. package/dist/ui/components/index.d.ts +1 -0
  42. package/dist/ui/components/index.js +1 -0
  43. package/dist/ui/components/input/Input.stories.d.ts +9 -0
  44. package/dist/ui/components/input/Input.stories.js +78 -0
  45. package/dist/ui/components/input/Input.svelte +39 -3
  46. package/dist/ui/components/input/types.d.ts +6 -0
  47. package/dist/ui/components/layout/Layout.svelte +7 -59
  48. package/dist/ui/components/layout/Layout.svelte.d.ts +2 -2
  49. package/dist/ui/components/layout/examples/ExampleLayout.svelte +22 -17
  50. package/dist/ui/components/layout/index.d.ts +1 -1
  51. package/dist/ui/components/layout/test-helpers/TestLayoutWithFooter.svelte +20 -0
  52. package/dist/ui/components/layout/test-helpers/TestLayoutWithFooter.svelte.d.ts +7 -0
  53. package/dist/ui/components/layout/types.d.ts +1 -10
  54. package/dist/ui/components/notification/Notification.stories.svelte +12 -1
  55. package/dist/ui/components/notification/Notification.svelte +10 -5
  56. package/dist/ui/components/notification/Notification.svelte.d.ts +1 -1
  57. package/dist/ui/components/notification/types.d.ts +1 -1
  58. package/dist/ui/components/section/Section.svelte +4 -2
  59. package/dist/ui/components/section/types.d.ts +8 -0
  60. package/dist/ui/components/text/Text.stories.svelte +67 -1
  61. package/dist/ui/components/text/Text.svelte +209 -8
  62. package/dist/ui/components/text/types.d.ts +4 -0
  63. package/dist/utils/animations/factory.d.ts +7 -0
  64. package/dist/utils/animations/factory.js +101 -0
  65. package/dist/utils/animations/index.d.ts +7 -0
  66. package/dist/utils/animations/index.js +62 -0
  67. package/dist/utils/animations/types.d.ts +39 -0
  68. package/dist/utils/animations/types.js +1 -0
  69. package/dist/utils/custom-code/configs.d.ts +22 -0
  70. package/dist/utils/custom-code/configs.js +40 -0
  71. package/dist/utils/custom-code/index.d.ts +1 -0
  72. package/dist/utils/custom-code/index.js +1 -0
  73. package/dist/utils/helpers/capitalizeFirstLetter.d.ts +4 -0
  74. package/dist/utils/helpers/capitalizeFirstLetter.js +9 -0
  75. package/dist/utils/helpers/getTimeNow.d.ts +4 -0
  76. package/dist/utils/helpers/getTimeNow.js +8 -0
  77. package/dist/utils/helpers/index.d.ts +4 -0
  78. package/dist/utils/helpers/index.js +4 -0
  79. package/dist/utils/helpers/minifyCode.d.ts +10 -0
  80. package/dist/utils/helpers/minifyCode.js +73 -0
  81. package/dist/utils/helpers/objectsToModuleExports.d.ts +1 -1
  82. package/dist/utils/helpers/objectsToModuleExports.js +1 -0
  83. package/dist/utils/helpers/toHumanReadableList.d.ts +4 -0
  84. package/dist/utils/helpers/toHumanReadableList.js +11 -0
  85. package/dist/utils/index.d.ts +1 -0
  86. package/dist/utils/index.js +1 -0
  87. package/dist/utils/webflow-canvas/getAllChildren.d.ts +16 -0
  88. package/dist/utils/webflow-canvas/getAllChildren.js +65 -0
  89. package/dist/utils/webflow-canvas/getElementClassList.d.ts +9 -0
  90. package/dist/utils/webflow-canvas/getElementClassList.js +19 -0
  91. package/dist/utils/webflow-canvas/index.d.ts +2 -0
  92. package/dist/utils/webflow-canvas/index.js +2 -0
  93. package/package.json +6 -1
  94. package/dist/router/README.md +0 -397
  95. /package/dist/router/{Link.svelte.d.ts → providers/Link.svelte.d.ts} +0 -0
@@ -0,0 +1,249 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { HTMLAttributes } from 'svelte/elements';
4
+
5
+ import { CopyIcon, EyeIcon } from '../../icons';
6
+ import { trimExtraSpaces } from '../../../utils/helpers';
7
+
8
+ import type { CopyTextProps } from './types';
9
+
10
+ let {
11
+ content,
12
+ title,
13
+ disabled = false,
14
+ raw = false,
15
+ hidden = false,
16
+ comment = '',
17
+ tooltip = 'Click to copy',
18
+ onNotify,
19
+ onCopy,
20
+ onError,
21
+ header,
22
+ footer,
23
+ class: className = '',
24
+ ...restProps
25
+ }: CopyTextProps = $props();
26
+
27
+ // Component state
28
+ let isCopied = $state(false);
29
+ let isCooldown = $state(false);
30
+
31
+ // Simple computed values to avoid infinite loops
32
+ function getProcessedContent() {
33
+ if (raw) {
34
+ return comment ? `<!-- ${comment} -->\n${content}` : content;
35
+ }
36
+ return trimExtraSpaces(content) ?? '';
37
+ }
38
+
39
+ function getCopyButtonClasses() {
40
+ return [
41
+ 'copy-button',
42
+ disabled ? 'copy-button--disabled' : '',
43
+ isCopied ? 'copy-button--copied' : '',
44
+ className || ''
45
+ ]
46
+ .filter(Boolean)
47
+ .join(' ');
48
+ }
49
+
50
+ /**
51
+ * Handles notifications with cooldown to prevent spam
52
+ */
53
+ const handleNotification = (type: 'Success' | 'Error', message: string) => {
54
+ if (!isCooldown) {
55
+ onNotify?.({ type, message });
56
+ isCooldown = true;
57
+ setTimeout(() => {
58
+ isCooldown = false;
59
+ }, 1000);
60
+ }
61
+ };
62
+
63
+ // Simple click handler without reactive effects to avoid infinite loops
64
+ function handleClick() {
65
+ if (disabled) return;
66
+
67
+ // Use modern clipboard API instead of ClipboardJS to avoid issues
68
+ const textToCopy = getProcessedContent();
69
+
70
+ if (navigator.clipboard) {
71
+ navigator.clipboard
72
+ .writeText(textToCopy)
73
+ .then(() => {
74
+ isCopied = true;
75
+ handleNotification('Success', 'Copied to clipboard!');
76
+ onCopy?.(textToCopy);
77
+
78
+ // Reset copied state after 2 seconds
79
+ setTimeout(() => {
80
+ isCopied = false;
81
+ }, 2000);
82
+ })
83
+ .catch(() => {
84
+ const errorMessage = 'Failed to copy. Please try again.';
85
+ handleNotification('Error', errorMessage);
86
+ onError?.(errorMessage);
87
+ });
88
+ } else {
89
+ // Fallback for browsers that don't support navigator.clipboard
90
+ const errorMessage = 'Clipboard not supported in this browser';
91
+ handleNotification('Error', errorMessage);
92
+ onError?.(errorMessage);
93
+ }
94
+ }
95
+ </script>
96
+
97
+ {#if !hidden}
98
+ <div class="copy-text" {...restProps}>
99
+ {#if header}
100
+ <div class="copy-text__header">
101
+ {@render header()}
102
+ </div>
103
+ {:else if title}
104
+ <div class="copy-text__header">
105
+ <h3 class="copy-text__title">{title}</h3>
106
+ </div>
107
+ {/if}
108
+
109
+ <div
110
+ class={getCopyButtonClasses()}
111
+ role="button"
112
+ tabindex="0"
113
+ aria-label={disabled ? 'Copy disabled' : tooltip}
114
+ title={tooltip}
115
+ onclick={handleClick}
116
+ onkeydown={(e) => {
117
+ if ((e.key === 'Enter' || e.key === ' ') && !disabled) {
118
+ e.preventDefault();
119
+ handleClick();
120
+ }
121
+ }}
122
+ >
123
+ <div class="copy-button__content" id="copy-content">
124
+ {getProcessedContent()}
125
+ </div>
126
+
127
+ <div class="copy-button__icon" aria-hidden="true">
128
+ {#if disabled}
129
+ <EyeIcon />
130
+ {:else}
131
+ <CopyIcon size={16} />
132
+ {/if}
133
+ </div>
134
+ </div>
135
+
136
+ {#if footer}
137
+ <div class="copy-text__footer">
138
+ {@render footer()}
139
+ </div>
140
+ {/if}
141
+ </div>
142
+ {/if}
143
+
144
+ <style>
145
+ .copy-text {
146
+ display: flex;
147
+ flex-direction: column;
148
+ gap: 12px;
149
+ width: 100%;
150
+ }
151
+
152
+ .copy-text__header {
153
+ display: flex;
154
+ align-items: center;
155
+ gap: 8px;
156
+ }
157
+
158
+ .copy-text__title {
159
+ margin: 0;
160
+ color: var(--text-color-primary, #ffffff);
161
+ font-family: Inter, sans-serif;
162
+ font-size: 12px;
163
+ font-weight: 500;
164
+ line-height: 16px;
165
+ }
166
+
167
+ .copy-button {
168
+ display: flex;
169
+ align-items: flex-start;
170
+ justify-content: space-between;
171
+ gap: 8px;
172
+ padding: 6px 8px;
173
+ border-radius: 4px;
174
+ background: var(
175
+ --button-secondary-background,
176
+ linear-gradient(180deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.1) 100%),
177
+ rgba(255, 255, 255, 0.08)
178
+ );
179
+ box-shadow:
180
+ 0px 0.5px 0.5px 0px rgba(255, 255, 255, 0.12) inset,
181
+ 0px 0.5px 1px 0px #000;
182
+ color: var(--text-color-secondary, #d9d9d9);
183
+ font-family: Inter, sans-serif;
184
+ font-size: 11px;
185
+ font-weight: 500;
186
+ line-height: 16px;
187
+ cursor: pointer;
188
+ transition: all 0.2s ease-in-out;
189
+ outline: none;
190
+ }
191
+
192
+ .copy-button:hover:not(.copy-button--disabled) {
193
+ background: var(
194
+ --button-secondary-background-hover,
195
+ linear-gradient(180deg, rgba(255, 255, 255, 0.16) 0%, rgba(255, 255, 255, 0.14) 100%),
196
+ rgba(255, 255, 255, 0.12)
197
+ );
198
+ }
199
+
200
+ .copy-button:focus-visible {
201
+ outline: 2px solid var(--focus-color, #0066cc);
202
+ outline-offset: 2px;
203
+ }
204
+
205
+ .copy-button--disabled {
206
+ background: none;
207
+ border: 1px solid var(--border-color-disabled, rgba(255, 255, 255, 0.2));
208
+ box-shadow: none;
209
+ opacity: 0.75;
210
+ cursor: not-allowed;
211
+ color: var(--text-color-disabled, #999999);
212
+ }
213
+
214
+ .copy-button__content {
215
+ display: flex;
216
+ align-items: flex-start;
217
+ flex: 1;
218
+ overflow: auto;
219
+ white-space: pre-wrap;
220
+ word-wrap: break-word;
221
+ max-width: 600px;
222
+ min-height: max-content;
223
+ }
224
+
225
+ .copy-button__content:disabled {
226
+ cursor: not-allowed;
227
+ color: var(--text-color-disabled, #999999);
228
+ }
229
+
230
+ .copy-button__icon {
231
+ display: flex;
232
+ align-items: center;
233
+ height: 16px;
234
+ flex-shrink: 0;
235
+ }
236
+
237
+ .copy-text__footer {
238
+ display: flex;
239
+ flex-direction: column;
240
+ gap: 8px;
241
+ }
242
+
243
+ /* Responsive design */
244
+ @media (max-width: 768px) {
245
+ .copy-button__content {
246
+ max-width: 300px;
247
+ }
248
+ }
249
+ </style>
@@ -0,0 +1,4 @@
1
+ import type { CopyTextProps } from './types';
2
+ declare const CopyText: import("svelte").Component<CopyTextProps, {}, "">;
3
+ type CopyText = ReturnType<typeof CopyText>;
4
+ export default CopyText;
@@ -0,0 +1,2 @@
1
+ export { default as CopyText } from './CopyText.svelte';
2
+ export type * from './types';
@@ -0,0 +1 @@
1
+ export { default as CopyText } from './CopyText.svelte';
@@ -0,0 +1,52 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ /**
4
+ * Notification function type for copy operations
5
+ */
6
+ export interface NotificationFunction {
7
+ (options: {
8
+ type: 'Success' | 'Error';
9
+ message: string;
10
+ }): void;
11
+ }
12
+ /**
13
+ * Props for the CopyText component
14
+ */
15
+ export interface CopyTextProps extends HTMLAttributes<HTMLDivElement> {
16
+ /** The content to be copied to clipboard */
17
+ content: string;
18
+ /** Optional title/heading text to display above the copy area */
19
+ title?: string;
20
+ /** Whether the copy functionality is disabled */
21
+ disabled?: boolean;
22
+ /** Whether to show the content in raw format (with HTML) or cleaned */
23
+ raw?: boolean;
24
+ /** Whether the component is in a hidden state */
25
+ hidden?: boolean;
26
+ /** Optional comment to prepend to copied content when in raw mode */
27
+ comment?: string;
28
+ /** Custom tooltip text for the copy button */
29
+ tooltip?: string;
30
+ /** Optional notification function to call on copy success/error */
31
+ onNotify?: NotificationFunction;
32
+ /** Callback fired when content is successfully copied */
33
+ onCopy?: (content: string) => void;
34
+ /** Callback fired when copy fails */
35
+ onError?: (error: string) => void;
36
+ /** Optional snippet for custom header content */
37
+ header?: Snippet;
38
+ /** Optional snippet for additional content below the copy area */
39
+ footer?: Snippet;
40
+ /** Custom CSS classes */
41
+ class?: string;
42
+ }
43
+ /**
44
+ * Event types for clipboard operations
45
+ */
46
+ export interface CopyEvent {
47
+ text: string;
48
+ clearSelection: () => void;
49
+ }
50
+ export interface CopyErrorEvent {
51
+ message: string;
52
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -3,6 +3,7 @@ export * from './button';
3
3
  export * from './button-group';
4
4
  export * from './checkbox';
5
5
  export * from './controlled-buttons';
6
+ export * from './copy-text';
6
7
  export * from './divider';
7
8
  export * from './iframe';
8
9
  export * from './input';
@@ -3,6 +3,7 @@ export * from './button';
3
3
  export * from './button-group';
4
4
  export * from './checkbox';
5
5
  export * from './controlled-buttons';
6
+ export * from './copy-text';
6
7
  export * from './divider';
7
8
  export * from './iframe';
8
9
  export * from './input';
@@ -90,6 +90,10 @@ declare const meta: {
90
90
  control: string;
91
91
  description: string;
92
92
  };
93
+ debounce: {
94
+ control: string;
95
+ description: string;
96
+ };
93
97
  };
94
98
  };
95
99
  export default meta;
@@ -131,3 +135,8 @@ export declare const WithMinLength: Story;
131
135
  export declare const InteractiveExample: Story;
132
136
  export declare const ComplexExample: Story;
133
137
  export declare const FormFieldExample: Story;
138
+ export declare const WithDebounce: Story;
139
+ export declare const FastDebounce: Story;
140
+ export declare const SlowDebounce: Story;
141
+ export declare const NoDebounce: Story;
142
+ export declare const DebounceWithSteppers: Story;
@@ -85,6 +85,10 @@ const meta = {
85
85
  max: {
86
86
  control: 'number',
87
87
  description: 'Maximum value for number input'
88
+ },
89
+ debounce: {
90
+ control: 'number',
91
+ description: 'Debounce delay in milliseconds for input events (0 = no debounce)'
88
92
  }
89
93
  }
90
94
  };
@@ -433,3 +437,77 @@ export const FormFieldExample = {
433
437
  invalid: true
434
438
  }
435
439
  };
440
+ // Debounce examples
441
+ export const WithDebounce = {
442
+ args: {
443
+ placeholder: 'Type fast to see debouncing...',
444
+ debounce: 300,
445
+ oninput: (value) => console.log('Debounced input:', value)
446
+ },
447
+ parameters: {
448
+ docs: {
449
+ description: {
450
+ story: 'Input with 300ms debounce. The oninput event will only fire after you stop typing for 300ms. Check the console to see the difference.'
451
+ }
452
+ }
453
+ }
454
+ };
455
+ export const FastDebounce = {
456
+ args: {
457
+ placeholder: 'Fast debounce (100ms)',
458
+ debounce: 100,
459
+ oninput: (value) => console.log('Fast debounced input:', value)
460
+ },
461
+ parameters: {
462
+ docs: {
463
+ description: {
464
+ story: 'Input with a faster 100ms debounce for more responsive but still controlled input handling.'
465
+ }
466
+ }
467
+ }
468
+ };
469
+ export const SlowDebounce = {
470
+ args: {
471
+ placeholder: 'Slow debounce (1000ms)',
472
+ debounce: 1000,
473
+ oninput: (value) => console.log('Slow debounced input:', value)
474
+ },
475
+ parameters: {
476
+ docs: {
477
+ description: {
478
+ story: 'Input with a slower 1000ms debounce for heavy operations that should only run after the user has finished typing.'
479
+ }
480
+ }
481
+ }
482
+ };
483
+ export const NoDebounce = {
484
+ args: {
485
+ placeholder: 'No debounce (immediate)',
486
+ debounce: 0,
487
+ oninput: (value) => console.log('Immediate input:', value)
488
+ },
489
+ parameters: {
490
+ docs: {
491
+ description: {
492
+ story: 'Input with no debouncing (debounce: 0). Events fire immediately on every keystroke.'
493
+ }
494
+ }
495
+ }
496
+ };
497
+ export const DebounceWithSteppers = {
498
+ args: {
499
+ type: 'number',
500
+ showSteppers: true,
501
+ value: '10',
502
+ debounce: 500,
503
+ placeholder: 'Debounced number input',
504
+ oninput: (value) => console.log('Debounced stepper input:', value)
505
+ },
506
+ parameters: {
507
+ docs: {
508
+ description: {
509
+ story: 'Number input with steppers and debouncing. Both typing and stepper clicks are debounced.'
510
+ }
511
+ }
512
+ }
513
+ };
@@ -28,6 +28,7 @@
28
28
  step = 1,
29
29
  min = undefined,
30
30
  max = undefined,
31
+ debounce = 0,
31
32
  oninput,
32
33
  onblur,
33
34
  onfocus,
@@ -59,6 +60,9 @@
59
60
  // Track HTML5 validation state
60
61
  let isValidationInvalid = $state(false);
61
62
 
63
+ // Debounce timer for input events
64
+ let debounceTimer: ReturnType<typeof setTimeout> | null = $state(null);
65
+
62
66
  // Use internal value for reactive state management
63
67
  let currInputValue = $derived(internalValue);
64
68
  let computedColor = $derived(currInputValue ? 'var(--actionPrimaryText)' : 'var(--text3)');
@@ -227,6 +231,37 @@
227
231
  }
228
232
  });
229
233
 
234
+ // Cleanup debounce timer on component destruction
235
+ $effect(() => {
236
+ return () => {
237
+ if (debounceTimer) {
238
+ clearTimeout(debounceTimer);
239
+ debounceTimer = null;
240
+ }
241
+ };
242
+ });
243
+
244
+ /**
245
+ * Debounced oninput handler
246
+ */
247
+ const debouncedOninput = (inputValue: string) => {
248
+ if (debounce > 0) {
249
+ // Clear existing timer
250
+ if (debounceTimer) {
251
+ clearTimeout(debounceTimer);
252
+ }
253
+
254
+ // Set new timer
255
+ debounceTimer = setTimeout(() => {
256
+ oninput?.(inputValue);
257
+ debounceTimer = null;
258
+ }, debounce);
259
+ } else {
260
+ // No debouncing, call immediately
261
+ oninput?.(inputValue);
262
+ }
263
+ };
264
+
230
265
  /**
231
266
  * Handles input events
232
267
  */
@@ -252,7 +287,8 @@
252
287
  // Update validation state for real-time feedback
253
288
  updateValidationState();
254
289
 
255
- oninput?.(inputValue);
290
+ // Call debounced oninput handler
291
+ debouncedOninput(inputValue);
256
292
  };
257
293
 
258
294
  /**
@@ -316,7 +352,7 @@
316
352
 
317
353
  // Trigger change events
318
354
  onValueChange?.(newValue);
319
- oninput?.(newStringValue);
355
+ debouncedOninput(newStringValue);
320
356
  };
321
357
 
322
358
  /**
@@ -345,7 +381,7 @@
345
381
 
346
382
  // Trigger change events
347
383
  onValueChange?.(newValue);
348
- oninput?.(newStringValue);
384
+ debouncedOninput(newStringValue);
349
385
  };
350
386
 
351
387
  /**
@@ -94,6 +94,12 @@ export interface InputProps extends Omit<HTMLInputAttributes, 'onblur' | 'onfocu
94
94
  * Maximum value for number input.
95
95
  */
96
96
  max?: number;
97
+ /**
98
+ * Debounce delay in milliseconds for input events.
99
+ * When set, oninput will be debounced by this amount.
100
+ * Default is no debouncing (0).
101
+ */
102
+ debounce?: number;
97
103
  /**
98
104
  * Event handler for input changes - receives the trimmed string value
99
105
  */
@@ -11,13 +11,12 @@
11
11
 
12
12
  import { Tooltip } from '../tooltip';
13
13
  import { EditModeMessage } from './common';
14
- import type { LayoutFooter, LayoutTab } from './types';
14
+ import type { LayoutTab } from './types';
15
15
 
16
16
  interface LayoutProps extends HTMLAttributes<HTMLDivElement> {
17
17
  activeTab: string;
18
18
  tabs: LayoutTab[];
19
19
  switchTab: (tab: string) => void;
20
- footer?: LayoutFooter;
21
20
  formKey: string;
22
21
  editMode?: boolean;
23
22
  showFooter?: boolean;
@@ -36,13 +35,13 @@
36
35
  sidebar?: Snippet;
37
36
  main?: Snippet;
38
37
  previewBar?: Snippet;
38
+ footer?: Snippet;
39
39
  }
40
40
 
41
41
  let {
42
42
  activeTab,
43
43
  tabs,
44
44
  switchTab,
45
- footer = { buttons: [] },
46
45
  formKey,
47
46
  editMode = false,
48
47
  showFooter = true,
@@ -56,12 +55,12 @@
56
55
  sidebar,
57
56
  main,
58
57
  previewBar,
58
+ footer,
59
59
  class: className = '',
60
60
  ...restProps
61
61
  }: LayoutProps = $props();
62
62
 
63
63
  // Derived states
64
- let hasFooterButtons = $derived(footer?.buttons && footer.buttons.length > 0);
65
64
  let containerStyles = $derived(
66
65
  containerMode ? 'height: 100%; width: 100%;' : 'height: 100vh; width: 100vw;'
67
66
  );
@@ -93,7 +92,7 @@
93
92
  }
94
93
 
95
94
  // Handle footer row
96
- if (showFooter && hasFooterButtons) {
95
+ if (showFooter && footer) {
97
96
  if (showSidebar) {
98
97
  areas.push('"sidebar footer"');
99
98
  } else {
@@ -118,7 +117,7 @@
118
117
 
119
118
  rows.push('1fr'); // main content area takes remaining space
120
119
 
121
- if (showFooter && hasFooterButtons) {
120
+ if (showFooter && footer) {
122
121
  rows.push(footerHeight);
123
122
  }
124
123
 
@@ -240,21 +239,9 @@
240
239
  </div>
241
240
 
242
241
  <!-- Footer -->
243
- {#if showFooter && hasFooterButtons}
242
+ {#if showFooter && footer}
244
243
  <div class="footer" data-area="footer">
245
- {#each footer.buttons as button (button.text)}
246
- <button
247
- class="footer-button footer-button--{button.variant}"
248
- disabled={editMode}
249
- onclick={button.onClick}
250
- >
251
- {#if button.icon}
252
- {@const Icon = button.icon}
253
- <Icon />
254
- {/if}
255
- {button.text}
256
- </button>
257
- {/each}
244
+ {@render footer()}
258
245
  </div>
259
246
  {/if}
260
247
  </div>
@@ -507,43 +494,4 @@
507
494
  height: 100%;
508
495
  overflow: hidden;
509
496
  }
510
-
511
- .footer-button {
512
- display: flex;
513
- padding: 4px 8px;
514
- justify-content: center;
515
- align-items: center;
516
- gap: 2px;
517
- border-radius: 4px;
518
- border: 1px solid var(--border-border-1, #363636);
519
- background: var(--background2);
520
- color: var(--text1);
521
- text-align: center;
522
- font-family: Inter;
523
- font-size: 11px;
524
- font-style: normal;
525
- font-weight: 400;
526
- line-height: 16px;
527
- cursor: pointer;
528
- transition: all 0.2s ease-in-out;
529
- }
530
-
531
- .footer-button--primary {
532
- background: var(--action-action-primary-background-hover, #1280ee);
533
- color: var(--actionPrimaryText);
534
- }
535
-
536
- .footer-button--secondary {
537
- background: var(--background2);
538
- color: var(--text1);
539
- }
540
-
541
- .footer-button:hover:not(:disabled) {
542
- opacity: 0.8;
543
- }
544
-
545
- .footer-button:disabled {
546
- opacity: 0.5;
547
- cursor: not-allowed;
548
- }
549
497
  </style>
@@ -1,11 +1,10 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { HTMLAttributes } from 'svelte/elements';
3
- import type { LayoutFooter, LayoutTab } from './types';
3
+ import type { LayoutTab } from './types';
4
4
  interface LayoutProps extends HTMLAttributes<HTMLDivElement> {
5
5
  activeTab: string;
6
6
  tabs: LayoutTab[];
7
7
  switchTab: (tab: string) => void;
8
- footer?: LayoutFooter;
9
8
  formKey: string;
10
9
  editMode?: boolean;
11
10
  showFooter?: boolean;
@@ -24,6 +23,7 @@ interface LayoutProps extends HTMLAttributes<HTMLDivElement> {
24
23
  sidebar?: Snippet;
25
24
  main?: Snippet;
26
25
  previewBar?: Snippet;
26
+ footer?: Snippet;
27
27
  }
28
28
  declare const Layout: import("svelte").Component<LayoutProps, {}, "">;
29
29
  type Layout = ReturnType<typeof Layout>;