sveltacular 1.0.21 → 1.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -50,7 +50,7 @@ npm i sveltacular
50
50
  import { Button } from 'sveltacular';
51
51
  </script>
52
52
 
53
- <Button variant="primary" label="Hello World" />
53
+ <Button variant="primary">Hello World</Button>
54
54
  ```
55
55
 
56
56
  **Note**: The stylesheet import is required for components to render with default styling. Import it once at the app level (not in individual components). If you prefer to provide your own theme, you can skip this import and define your own CSS variables (see [Theming](#theming) below).
@@ -6,10 +6,9 @@
6
6
  import type { Snippet } from 'svelte';
7
7
  import { navigateTo } from '../../helpers/navigate-to.js';
8
8
  import type { ButtonVariant, FormFieldSizeOptions } from '../../types/form.js';
9
+ import Spinner from '../../generic/spinner/spinner.svelte';
9
10
 
10
11
  let {
11
- /** Button label text */
12
- label,
13
12
  /** Optional href for navigation */
14
13
  href = undefined,
15
14
  /** Button size */
@@ -26,7 +25,7 @@
26
25
  disabled = $bindable(false),
27
26
  /** Loading state */
28
27
  loading = false,
29
- /** ARIA label override */
28
+ /** ARIA label for accessibility (required if button content is not text) */
30
29
  ariaLabel = undefined,
31
30
  /** Remove margins */
32
31
  noMargin = false,
@@ -36,10 +35,9 @@
36
35
  repeatSubmitDelay = 500,
37
36
  /** Click handler */
38
37
  onClick = undefined,
39
- /** Optional children snippet */
38
+ /** Button content */
40
39
  children
41
40
  }: {
42
- label?: string;
43
41
  href?: string | undefined;
44
42
  size?: FormFieldSizeOptions;
45
43
  variant?: ButtonVariant;
@@ -53,12 +51,12 @@
53
51
  collapse?: boolean;
54
52
  repeatSubmitDelay?: number | 'infinite';
55
53
  onClick?: ((e?: Event) => void) | undefined;
56
- children?: Snippet;
54
+ children: Snippet;
57
55
  } = $props();
58
56
 
59
57
  let isDisabled = $derived(disabled || loading);
60
58
 
61
- const click = (e: Event) => {
59
+ const handleClick = (e: Event) => {
62
60
  if (isDisabled) {
63
61
  e.preventDefault();
64
62
  e.stopPropagation();
@@ -79,25 +77,23 @@
79
77
 
80
78
  <button
81
79
  {type}
82
- onclick={click}
80
+ onclick={handleClick}
83
81
  class="{size} {variant} {flex ? 'flex' : ''}"
84
82
  class:block
85
83
  class:noMargin
86
84
  class:collapse
87
85
  class:loading
88
86
  disabled={isDisabled}
89
- aria-label={ariaLabel || label}
87
+ aria-label={ariaLabel}
90
88
  aria-busy={loading}
91
89
  aria-disabled={isDisabled}
92
90
  >
93
91
  {#if loading}
94
- <span class="loading-indicator" aria-hidden="true">⏳</span>
95
- {/if}
96
- {#if children}
97
- {@render children()}
98
- {:else if label}
99
- <span class="label">{label}</span>
92
+ <span class="spinner-wrapper" aria-hidden="true">
93
+ <Spinner {size} variant={variant === 'outline' ? 'secondary' : 'primary'} />
94
+ </span>
100
95
  {/if}
96
+ {@render children()}
101
97
  </button>
102
98
 
103
99
  <style>button {
@@ -228,4 +224,10 @@ button.link {
228
224
  }
229
225
  button.link:hover {
230
226
  color: var(--base-link-hover-fg);
227
+ }
228
+ button .spinner-wrapper {
229
+ display: inline-flex;
230
+ align-items: center;
231
+ margin-right: var(--spacing-xs);
232
+ vertical-align: middle;
231
233
  }</style>
@@ -5,7 +5,6 @@
5
5
  import type { Snippet } from 'svelte';
6
6
  import type { ButtonVariant, FormFieldSizeOptions } from '../../types/form.js';
7
7
  type $$ComponentProps = {
8
- label?: string;
9
8
  href?: string | undefined;
10
9
  size?: FormFieldSizeOptions;
11
10
  variant?: ButtonVariant;
@@ -19,7 +18,7 @@ type $$ComponentProps = {
19
18
  collapse?: boolean;
20
19
  repeatSubmitDelay?: number | 'infinite';
21
20
  onClick?: ((e?: Event) => void) | undefined;
22
- children?: Snippet;
21
+ children: Snippet;
23
22
  };
24
23
  declare const Button: import("svelte").Component<$$ComponentProps, {}, "disabled">;
25
24
  type Button = ReturnType<typeof Button>;
@@ -152,14 +152,11 @@
152
152
  </FormInputWrapper>
153
153
  {#if steps.length > 0}
154
154
  <span class="steps">
155
- {#each steps as step}
156
- <Button
157
- noMargin={true}
158
- collapse={true}
159
- onClick={() => incrementValue(step)}
160
- label={step.label}
161
- />
162
- {/each}
155
+ {#each steps as step}
156
+ <Button noMargin={true} collapse={true} onClick={() => incrementValue(step)}>
157
+ {step.label}
158
+ </Button>
159
+ {/each}
163
160
  </span>
164
161
  {/if}
165
162
  </div>
@@ -0,0 +1,128 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import Button from '../button/button.svelte';
4
+ import type { ButtonVariant } from '../../types';
5
+
6
+ export type AdditionalButton = {
7
+ type?: 'button' | 'submit' | 'reset';
8
+ variant?: ButtonVariant;
9
+ text: string;
10
+ onClick?: ((e?: Event) => void) | undefined;
11
+ disabled?: boolean;
12
+ };
13
+
14
+ let {
15
+ children,
16
+ disabled = false,
17
+ onCancel = undefined,
18
+ onSubmit = undefined,
19
+ cancelText = 'Cancel',
20
+ cancelVariant = 'secondary',
21
+ submitText = 'Submit',
22
+ submitVariant = 'primary',
23
+ additionalButtons = []
24
+ }: {
25
+ children?: Snippet;
26
+ disabled?: boolean;
27
+ onCancel?: (() => void) | undefined;
28
+ cancelText?: string;
29
+ cancelVariant?: ButtonVariant;
30
+ submitText?: string;
31
+ submitVariant?: ButtonVariant;
32
+ onSubmit?: (() => void) | undefined;
33
+ additionalButtons?: AdditionalButton[];
34
+ } = $props();
35
+
36
+ const buttons = $derived(
37
+ [
38
+ onCancel
39
+ ? {
40
+ type: 'button' as const,
41
+ variant: cancelVariant,
42
+ text: cancelText,
43
+ onClick: onCancel,
44
+ disabled
45
+ }
46
+ : undefined,
47
+ ...additionalButtons.map((btn) => ({ ...btn, disabled: btn.disabled || disabled })),
48
+ {
49
+ type: 'submit' as const,
50
+ variant: submitVariant,
51
+ text: submitText,
52
+ onClick: onSubmit,
53
+ disabled
54
+ }
55
+ ].filter(Boolean) as AdditionalButton[]
56
+ );
57
+ </script>
58
+
59
+ <div class="form-actions">
60
+ {#if children}
61
+ <div class="content">
62
+ {@render children()}
63
+ </div>
64
+ {/if}
65
+ <div class="buttons">
66
+ {#each buttons as button}
67
+ <Button
68
+ type={button.type ?? 'button'}
69
+ variant={button.variant ?? 'secondary'}
70
+ onClick={button.onClick}
71
+ disabled={button.disabled}
72
+ >
73
+ {button.text}
74
+ </Button>
75
+ {/each}
76
+ </div>
77
+ </div>
78
+
79
+ <style>/* ============================================
80
+ BREAKPOINTS - Responsive Design
81
+ ============================================ */
82
+ .form-actions {
83
+ display: flex;
84
+ flex-direction: row;
85
+ align-items: center;
86
+ justify-content: space-between;
87
+ gap: var(--spacing-md);
88
+ /* Vertical spacing - separate form from actions and actions from content below */
89
+ margin-top: var(--spacing-xl);
90
+ margin-bottom: var(--spacing-lg);
91
+ padding-top: var(--spacing-lg);
92
+ /* Optional: Add a subtle top border to visually separate from form content */
93
+ border-top: 1px solid var(--gray-200);
94
+ }
95
+ .form-actions .content {
96
+ display: flex;
97
+ flex-direction: row;
98
+ align-items: center;
99
+ gap: var(--spacing-sm);
100
+ flex: 0 1 auto; /* Don't grow, can shrink, auto basis */
101
+ }
102
+ .form-actions .buttons {
103
+ display: flex;
104
+ flex-direction: row;
105
+ align-items: center;
106
+ gap: var(--spacing-sm);
107
+ flex: 0 0 auto; /* Don't grow, don't shrink, auto basis */
108
+ margin-left: auto; /* Push to the right */
109
+ }
110
+ .form-actions {
111
+ /* Responsive: Stack on mobile */
112
+ }
113
+ @media (max-width: 479.98px) {
114
+ .form-actions {
115
+ flex-direction: column;
116
+ align-items: stretch;
117
+ gap: var(--spacing-md);
118
+ }
119
+ .form-actions .content,
120
+ .form-actions .buttons {
121
+ width: 100%;
122
+ justify-content: flex-start;
123
+ }
124
+ .form-actions .buttons {
125
+ margin-left: 0;
126
+ justify-content: flex-end;
127
+ }
128
+ }</style>
@@ -0,0 +1,23 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { ButtonVariant } from '../../types';
3
+ export type AdditionalButton = {
4
+ type?: 'button' | 'submit' | 'reset';
5
+ variant?: ButtonVariant;
6
+ text: string;
7
+ onClick?: ((e?: Event) => void) | undefined;
8
+ disabled?: boolean;
9
+ };
10
+ type $$ComponentProps = {
11
+ children?: Snippet;
12
+ disabled?: boolean;
13
+ onCancel?: (() => void) | undefined;
14
+ cancelText?: string;
15
+ cancelVariant?: ButtonVariant;
16
+ submitText?: string;
17
+ submitVariant?: ButtonVariant;
18
+ onSubmit?: (() => void) | undefined;
19
+ additionalButtons?: AdditionalButton[];
20
+ };
21
+ declare const FormActions: import("svelte").Component<$$ComponentProps, {}, "">;
22
+ type FormActions = ReturnType<typeof FormActions>;
23
+ export default FormActions;
@@ -21,10 +21,12 @@ export * from './list-box/index.js';
21
21
  export * from './phone-box/index.js';
22
22
  export * from './radio-group/index.js';
23
23
  export { default as Form } from './form.svelte';
24
+ export { default as FormActions } from './form-actions/form-actions.svelte';
24
25
  export { default as FormField } from './form-field/form-field.svelte';
25
26
  export { default as FormFooter } from './form-footer.svelte';
26
27
  export { default as FormHeader } from './form-header.svelte';
27
28
  export { default as FormLabel } from './form-label/form-label.svelte';
28
29
  export { default as FormSection } from './form-section/form-section.svelte';
29
30
  export { default as FormRow } from './form-row/form-row.svelte';
31
+ export type { AdditionalButton } from './form-actions/form-actions.svelte';
30
32
  export * from './validation.js';
@@ -24,6 +24,7 @@ export * from './phone-box/index.js';
24
24
  export * from './radio-group/index.js';
25
25
  // Form structure components
26
26
  export { default as Form } from './form.svelte';
27
+ export { default as FormActions } from './form-actions/form-actions.svelte';
27
28
  export { default as FormField } from './form-field/form-field.svelte';
28
29
  export { default as FormFooter } from './form-footer.svelte';
29
30
  export { default as FormHeader } from './form-header.svelte';
@@ -54,10 +54,10 @@
54
54
  <DialogBody>
55
55
  {@render children?.()}
56
56
  </DialogBody>
57
- <Divider />
58
- <DialogFooter>
59
- <Button onClick={close} size="full" variant={buttonVariant} label={buttonText} />
60
- </DialogFooter>
57
+ <Divider />
58
+ <DialogFooter>
59
+ <Button onClick={close} size="full" variant={buttonVariant}>{buttonText}</Button>
60
+ </DialogFooter>
61
61
  </Dialog>
62
62
  </Overlay>
63
63
  {/if}
@@ -65,11 +65,11 @@
65
65
  <DialogBody>
66
66
  {@render children?.()}
67
67
  </DialogBody>
68
- <Divider />
69
- <DialogFooter>
70
- <Button onClick={no} variant={noVariant} size="full" label={noText} />
71
- <Button onClick={yes} variant={yesVariant} size="full" label={yesText} />
72
- </DialogFooter>
68
+ <Divider />
69
+ <DialogFooter>
70
+ <Button onClick={no} variant={noVariant} size="full">{noText}</Button>
71
+ <Button onClick={yes} variant={yesVariant} size="full">{yesText}</Button>
72
+ </DialogFooter>
73
73
  </Dialog>
74
74
  </Overlay>
75
75
  {/if}
@@ -78,11 +78,11 @@
78
78
  {@render children()}
79
79
  {/if}
80
80
  </DialogBody>
81
- <Divider />
82
- <DialogFooter>
83
- <Button onClick={no} variant={cancelVariant} size="full" label={cancelText} />
84
- <Button onClick={yes} variant={okVariant} size="full" label={okText} />
85
- </DialogFooter>
81
+ <Divider />
82
+ <DialogFooter>
83
+ <Button onClick={no} variant={cancelVariant} size="full">{cancelText}</Button>
84
+ <Button onClick={yes} variant={okVariant} size="full">{okText}</Button>
85
+ </DialogFooter>
86
86
  </Dialog>
87
87
  </Overlay>
88
88
  {/if}
@@ -126,7 +126,7 @@
126
126
  <footer>
127
127
  <div>
128
128
  {#if !isFirstStep}
129
- <Button type="button" variant="secondary" onClick={previous} {disabled} label="Previous" />
129
+ <Button type="button" variant="secondary" onClick={previous} {disabled}>Previous</Button>
130
130
  {/if}
131
131
  </div>
132
132
  <div>
@@ -134,9 +134,9 @@
134
134
  </div>
135
135
  <div>
136
136
  {#if isLastStep}
137
- <Button type="submit" variant="primary" onClick={done} {disabled} label="Done" />
137
+ <Button type="submit" variant="primary" onClick={done} {disabled}>Done</Button>
138
138
  {:else}
139
- <Button type="button" variant="primary" onClick={next} {disabled} label="Next" />
139
+ <Button type="button" variant="primary" onClick={next} {disabled}>Next</Button>
140
140
  {/if}
141
141
  </div>
142
142
  </footer>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sveltacular",
3
- "version": "1.0.21",
3
+ "version": "1.0.24",
4
4
  "description": "A Svelte component library",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",