@groundbrick/svelte-ui 0.1.1

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 (60) hide show
  1. package/README.md +125 -0
  2. package/dist/components/Alert.svelte +335 -0
  3. package/dist/components/Alert.svelte.d.ts +24 -0
  4. package/dist/components/AutocompleteInput.svelte +356 -0
  5. package/dist/components/AutocompleteInput.svelte.d.ts +72 -0
  6. package/dist/components/Badge.svelte +185 -0
  7. package/dist/components/Badge.svelte.d.ts +20 -0
  8. package/dist/components/Button.svelte +415 -0
  9. package/dist/components/Button.svelte.d.ts +34 -0
  10. package/dist/components/Card.svelte +181 -0
  11. package/dist/components/Card.svelte.d.ts +24 -0
  12. package/dist/components/CardBody.svelte +78 -0
  13. package/dist/components/CardBody.svelte.d.ts +12 -0
  14. package/dist/components/CardFooter.svelte +81 -0
  15. package/dist/components/CardFooter.svelte.d.ts +14 -0
  16. package/dist/components/CardHeader.svelte +186 -0
  17. package/dist/components/CardHeader.svelte.d.ts +21 -0
  18. package/dist/components/Col.svelte +172 -0
  19. package/dist/components/Col.svelte.d.ts +26 -0
  20. package/dist/components/Container.svelte +118 -0
  21. package/dist/components/Container.svelte.d.ts +14 -0
  22. package/dist/components/Drawer.svelte +233 -0
  23. package/dist/components/Drawer.svelte.d.ts +13 -0
  24. package/dist/components/Dropdown.svelte +190 -0
  25. package/dist/components/Dropdown.svelte.d.ts +26 -0
  26. package/dist/components/DropdownItem.svelte +103 -0
  27. package/dist/components/DropdownItem.svelte.d.ts +22 -0
  28. package/dist/components/DurationInput.svelte +170 -0
  29. package/dist/components/DurationInput.svelte.d.ts +27 -0
  30. package/dist/components/EditableTable.svelte +647 -0
  31. package/dist/components/EditableTable.svelte.d.ts +74 -0
  32. package/dist/components/EmptyState.svelte +192 -0
  33. package/dist/components/EmptyState.svelte.d.ts +22 -0
  34. package/dist/components/FormField.svelte +260 -0
  35. package/dist/components/FormField.svelte.d.ts +68 -0
  36. package/dist/components/GridView.svelte +1022 -0
  37. package/dist/components/GridView.svelte.d.ts +38 -0
  38. package/dist/components/GridView.types.d.ts +28 -0
  39. package/dist/components/GridView.types.js +1 -0
  40. package/dist/components/LoadingSpinner.svelte +253 -0
  41. package/dist/components/LoadingSpinner.svelte.d.ts +17 -0
  42. package/dist/components/Modal.svelte +473 -0
  43. package/dist/components/Modal.svelte.d.ts +42 -0
  44. package/dist/components/PhoneInput.svelte +406 -0
  45. package/dist/components/PhoneInput.svelte.d.ts +31 -0
  46. package/dist/components/PhotoUpload.svelte +529 -0
  47. package/dist/components/PhotoUpload.svelte.d.ts +46 -0
  48. package/dist/components/Row.svelte +153 -0
  49. package/dist/components/Row.svelte.d.ts +18 -0
  50. package/dist/icons/PawPrintIcon.svelte +41 -0
  51. package/dist/icons/PawPrintIcon.svelte.d.ts +14 -0
  52. package/dist/index.d.ts +41 -0
  53. package/dist/index.js +49 -0
  54. package/dist/styles/forms.css +182 -0
  55. package/dist/styles/tokens.css +243 -0
  56. package/dist/utils/duration.d.ts +20 -0
  57. package/dist/utils/duration.js +40 -0
  58. package/dist/utils/scrollLock.d.ts +7 -0
  59. package/dist/utils/scrollLock.js +26 -0
  60. package/package.json +66 -0
@@ -0,0 +1,192 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import PawPrintIcon from '../icons/PawPrintIcon.svelte';
4
+
5
+ export interface EmptyStateProps {
6
+ /** Empty state title */
7
+ title: string;
8
+ /** Description or message */
9
+ description?: string;
10
+ /** Alias for description */
11
+ message?: string;
12
+ /** Bootstrap Icons class (e.g. "bi-inbox") */
13
+ icon?: string;
14
+ /** Predefined illustration */
15
+ illustration?: 'inbox' | 'search' | 'pets' | 'appointments' | 'calendar' | 'users' | 'files';
16
+ /** Actions/buttons to display */
17
+ children?: Snippet;
18
+ /** Additional CSS classes */
19
+ class?: string;
20
+ /** Compact mode (reduces padding and sizes) */
21
+ compact?: boolean;
22
+ }
23
+
24
+ let {
25
+ title,
26
+ description,
27
+ message,
28
+ icon,
29
+ illustration,
30
+ children,
31
+ class: additionalClasses = '',
32
+ compact = false
33
+ }: EmptyStateProps = $props();
34
+
35
+ // Support both description and message for flexibility
36
+ const effectiveDescription = $derived(description || message);
37
+
38
+ // Illustration to Bootstrap icon mapping.
39
+ // `pets` is handled separately below (renders PawPrintIcon).
40
+ const illustrations: Record<string, string> = {
41
+ inbox: 'bi-inbox',
42
+ search: 'bi-search',
43
+ appointments: 'bi-calendar-x',
44
+ calendar: 'bi-calendar3',
45
+ users: 'bi-people',
46
+ files: 'bi-file-earmark-text'
47
+ };
48
+
49
+ const usePawPrint = $derived(icon === 'paw-print' || illustration === 'pets');
50
+
51
+ // Which icon to use (only when not paw-print)
52
+ const iconClass = $derived(icon || (illustration ? illustrations[illustration] : 'bi-inbox'));
53
+
54
+ const emptyStateClass = $derived([
55
+ 'empty-state',
56
+ compact ? 'empty-state-compact' : '',
57
+ additionalClasses
58
+ ].filter(Boolean).join(' '));
59
+ </script>
60
+
61
+ <div class={emptyStateClass}>
62
+ <div class="empty-state-icon">
63
+ {#if usePawPrint}
64
+ <PawPrintIcon size="4rem" />
65
+ {:else}
66
+ <i class="bi {iconClass}"></i>
67
+ {/if}
68
+ </div>
69
+
70
+ <h3 class="empty-state-title">{title}</h3>
71
+
72
+ {#if effectiveDescription}
73
+ <p class="empty-state-description">{effectiveDescription}</p>
74
+ {/if}
75
+
76
+ {#if children}
77
+ <div class="empty-state-actions">
78
+ {@render children()}
79
+ </div>
80
+ {/if}
81
+ </div>
82
+
83
+ <style>
84
+ .empty-state {
85
+ display: flex;
86
+ flex-direction: column;
87
+ align-items: center;
88
+ justify-content: center;
89
+ padding: var(--spacing-2xl) var(--spacing-lg);
90
+ text-align: center;
91
+ min-height: 300px;
92
+ }
93
+
94
+ .empty-state-icon {
95
+ margin-bottom: var(--spacing-lg);
96
+ color: var(--color-gray-400);
97
+ animation: float 3s ease-in-out infinite;
98
+ }
99
+
100
+ .empty-state-icon i {
101
+ font-size: 4rem;
102
+ display: block;
103
+ }
104
+
105
+ .empty-state-title {
106
+ font-size: var(--font-size-xl);
107
+ font-weight: var(--font-weight-semibold);
108
+ color: var(--color-gray-700);
109
+ margin-bottom: var(--spacing-sm);
110
+ margin-top: 0;
111
+ }
112
+
113
+ .empty-state-description {
114
+ font-size: var(--font-size-base);
115
+ color: var(--color-gray-600);
116
+ max-width: 400px;
117
+ margin: 0 auto var(--spacing-lg);
118
+ line-height: var(--line-height-relaxed);
119
+ }
120
+
121
+ .empty-state-actions {
122
+ display: flex;
123
+ gap: var(--spacing-md);
124
+ flex-wrap: wrap;
125
+ justify-content: center;
126
+ margin-top: var(--spacing-md);
127
+ }
128
+
129
+ /* Compact mode */
130
+ .empty-state-compact {
131
+ padding: var(--spacing-lg) var(--spacing-md);
132
+ min-height: 200px;
133
+ }
134
+
135
+ .empty-state-compact .empty-state-icon {
136
+ margin-bottom: var(--spacing-md);
137
+ }
138
+
139
+ .empty-state-compact .empty-state-icon i {
140
+ font-size: 2.5rem;
141
+ }
142
+
143
+ .empty-state-compact .empty-state-title {
144
+ font-size: var(--font-size-lg);
145
+ margin-bottom: var(--spacing-xs);
146
+ }
147
+
148
+ .empty-state-compact .empty-state-description {
149
+ font-size: var(--font-size-sm);
150
+ margin-bottom: var(--spacing-md);
151
+ }
152
+
153
+ /* Smooth floating animation */
154
+ @keyframes float {
155
+ 0%, 100% {
156
+ transform: translateY(0);
157
+ }
158
+ 50% {
159
+ transform: translateY(-10px);
160
+ }
161
+ }
162
+
163
+ /* Responsive */
164
+ @media (max-width: 576px) {
165
+ .empty-state {
166
+ padding: var(--spacing-xl) var(--spacing-md);
167
+ min-height: 250px;
168
+ }
169
+
170
+ .empty-state-icon i {
171
+ font-size: 3rem;
172
+ }
173
+
174
+ .empty-state-title {
175
+ font-size: var(--font-size-lg);
176
+ }
177
+
178
+ .empty-state-description {
179
+ font-size: var(--font-size-sm);
180
+ }
181
+
182
+ .empty-state-actions {
183
+ flex-direction: column;
184
+ width: 100%;
185
+ }
186
+
187
+ .empty-state-actions :global(button),
188
+ .empty-state-actions :global(a) {
189
+ width: 100%;
190
+ }
191
+ }
192
+ </style>
@@ -0,0 +1,22 @@
1
+ import type { Snippet } from "svelte";
2
+ export interface EmptyStateProps {
3
+ /** Empty state title */
4
+ title: string;
5
+ /** Description or message */
6
+ description?: string;
7
+ /** Alias for description */
8
+ message?: string;
9
+ /** Bootstrap Icons class (e.g. "bi-inbox") */
10
+ icon?: string;
11
+ /** Predefined illustration */
12
+ illustration?: 'inbox' | 'search' | 'pets' | 'appointments' | 'calendar' | 'users' | 'files';
13
+ /** Actions/buttons to display */
14
+ children?: Snippet;
15
+ /** Additional CSS classes */
16
+ class?: string;
17
+ /** Compact mode (reduces padding and sizes) */
18
+ compact?: boolean;
19
+ }
20
+ declare const EmptyState: import("svelte").Component<EmptyStateProps, {}, "">;
21
+ type EmptyState = ReturnType<typeof EmptyState>;
22
+ export default EmptyState;
@@ -0,0 +1,260 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import "../styles/forms.css";
4
+
5
+ interface FormFieldProps {
6
+ /** ID único do campo */
7
+ id: string;
8
+ /** Label do campo */
9
+ label: string;
10
+ /** Tipo do campo */
11
+ type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'date' | 'time' | 'datetime-local' | 'textarea' | 'select' | 'search';
12
+ /** Valor do campo (bindable) */
13
+ value?: string | number;
14
+ /** Placeholder */
15
+ placeholder?: string;
16
+ /** Campo obrigatório */
17
+ required?: boolean;
18
+ /** Campo desabilitado */
19
+ disabled?: boolean;
20
+ /** Somente leitura */
21
+ readonly?: boolean;
22
+ /** Mensagem de erro */
23
+ error?: string;
24
+ /** Texto de ajuda */
25
+ helpText?: string;
26
+ /** Número de linhas (para textarea) */
27
+ rows?: number;
28
+ /** Opções (para select) */
29
+ options?: Array<{ value: string | number; label: string }>;
30
+ /** Valor mínimo (para number/date) */
31
+ min?: number | string;
32
+ /** Valor máximo (para number/date) */
33
+ max?: number | string;
34
+ /** Step (para number) */
35
+ step?: number;
36
+ /** Pattern (regex) */
37
+ pattern?: string;
38
+ /** Autocomplete */
39
+ autocomplete?: HTMLInputElement['autocomplete'];
40
+ /** Tamanho mínimo (para text/password) */
41
+ minlength?: number;
42
+ /** Tamanho máximo (para text/password) */
43
+ maxlength?: number;
44
+ /** Callback de mudança */
45
+ onchange?: (value: any) => void;
46
+ /** Callback de input */
47
+ oninput?: (value: any) => void;
48
+ /** Callback de blur */
49
+ onblur?: () => void;
50
+ /** Callback de focus */
51
+ onfocus?: () => void;
52
+ /** Callback de keydown */
53
+ onkeydown?: (e: KeyboardEvent) => void;
54
+ /** Ícone do campo (exibido no label) */
55
+ icon?: Snippet;
56
+ /** Ícone dentro do input (à esquerda) */
57
+ inputIcon?: Snippet;
58
+ /** Addon à direita do input (ex: loading spinner) */
59
+ inputAddon?: Snippet;
60
+ /** Classes CSS adicionais */
61
+ class?: string;
62
+ /** Referência ao elemento input */
63
+ inputRef?: HTMLInputElement;
64
+ }
65
+
66
+ let {
67
+ id,
68
+ label,
69
+ type = 'text',
70
+ value = $bindable(''),
71
+ placeholder,
72
+ required = false,
73
+ disabled = false,
74
+ readonly = false,
75
+ error,
76
+ helpText,
77
+ rows = 3,
78
+ options = [],
79
+ min,
80
+ max,
81
+ step,
82
+ pattern,
83
+ autocomplete,
84
+ minlength,
85
+ maxlength,
86
+ onchange,
87
+ oninput,
88
+ onblur,
89
+ onfocus,
90
+ onkeydown,
91
+ icon,
92
+ inputIcon,
93
+ inputAddon,
94
+ class: additionalClasses = '',
95
+ inputRef = $bindable()
96
+ }: FormFieldProps = $props();
97
+
98
+ // Handler de input
99
+ function handleInput(event: Event) {
100
+ const target = event.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
101
+
102
+ if (type === 'number') {
103
+ value = target.value ? Number(target.value) : '';
104
+ } else {
105
+ value = target.value;
106
+ }
107
+
108
+ oninput?.(value);
109
+ }
110
+
111
+ // Handler de change
112
+ function handleChange(event: Event) {
113
+ const target = event.target as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
114
+
115
+ if (type === 'number') {
116
+ value = target.value ? Number(target.value) : '';
117
+ } else if (type === 'select') {
118
+ // Preserve the original option value type (e.g., number) instead of the
119
+ // DOM-stringified target.value, so bind:value can match the option correctly.
120
+ const selectedStr = target.value;
121
+ const matchingOption = options.find(opt => String(opt.value) === selectedStr);
122
+ value = matchingOption ? matchingOption.value : selectedStr;
123
+ } else {
124
+ value = target.value;
125
+ }
126
+
127
+ onchange?.(value);
128
+ }
129
+
130
+ // Handler de blur
131
+ function handleBlur() {
132
+ onblur?.();
133
+ }
134
+
135
+ // Classes dinâmicas
136
+ const fieldClass = $derived([
137
+ 'ap-form-control',
138
+ type === 'select' ? 'ap-form-select' : '',
139
+ error ? 'ap-form-control-invalid' : '',
140
+ inputIcon ? 'ap-form-control-with-icon' : ''
141
+ ].filter(Boolean).join(' '));
142
+
143
+ const containerClass = $derived([
144
+ 'ap-form-field',
145
+ additionalClasses
146
+ ].filter(Boolean).join(' '));
147
+ </script>
148
+
149
+ <div class={containerClass}>
150
+ <label for={id} class="ap-form-label">
151
+ {#if icon}
152
+ <span class="ap-form-label-icon">
153
+ {@render icon()}
154
+ </span>
155
+ {/if}
156
+ {label}
157
+ {#if required}
158
+ <span class="ap-form-required" aria-label="obrigatório">*</span>
159
+ {/if}
160
+ </label>
161
+
162
+ <div class="ap-form-input-wrapper" class:has-input-icon={inputIcon} class:has-addon={inputAddon}>
163
+ {#if inputIcon}
164
+ <span class="ap-form-input-icon">
165
+ {@render inputIcon()}
166
+ </span>
167
+ {/if}
168
+
169
+ {#if type === 'textarea'}
170
+ <textarea
171
+ {id}
172
+ class={fieldClass}
173
+ {placeholder}
174
+ {required}
175
+ {disabled}
176
+ {readonly}
177
+ {rows}
178
+ {autocomplete}
179
+ value={value}
180
+ oninput={handleInput}
181
+ onchange={handleChange}
182
+ onblur={handleBlur}
183
+ aria-invalid={error ? 'true' : undefined}
184
+ aria-describedby={error ? `${id}-error` : helpText ? `${id}-help` : undefined}
185
+ ></textarea>
186
+ {:else if type === 'select'}
187
+ <select
188
+ {id}
189
+ class={fieldClass}
190
+ {required}
191
+ {disabled}
192
+ {autocomplete}
193
+ bind:value
194
+ onchange={handleChange}
195
+ onblur={handleBlur}
196
+ aria-invalid={error ? 'true' : undefined}
197
+ aria-describedby={error ? `${id}-error` : helpText ? `${id}-help` : undefined}
198
+ >
199
+ {#if placeholder}
200
+ <option value="" disabled selected={!value}>
201
+ {placeholder}
202
+ </option>
203
+ {/if}
204
+ {#each options as option}
205
+ <option value={option.value}>
206
+ {option.label}
207
+ </option>
208
+ {/each}
209
+ </select>
210
+ {:else}
211
+ <input
212
+ bind:this={inputRef}
213
+ {id}
214
+ type={type}
215
+ class={fieldClass}
216
+ {placeholder}
217
+ {required}
218
+ {disabled}
219
+ {readonly}
220
+ {min}
221
+ {max}
222
+ {step}
223
+ {pattern}
224
+ {autocomplete}
225
+ {minlength}
226
+ {maxlength}
227
+ value={value}
228
+ oninput={handleInput}
229
+ onchange={handleChange}
230
+ onblur={handleBlur}
231
+ onfocus={onfocus}
232
+ onkeydown={onkeydown}
233
+ aria-invalid={error ? 'true' : undefined}
234
+ aria-describedby={error ? `${id}-error` : helpText ? `${id}-help` : undefined}
235
+ />
236
+ {/if}
237
+
238
+ {#if inputAddon}
239
+ <span class="ap-form-input-addon">
240
+ {@render inputAddon()}
241
+ </span>
242
+ {/if}
243
+ </div>
244
+
245
+ {#if error}
246
+ <div id="{id}-error" class="ap-form-error">
247
+ <i class="bi bi-exclamation-circle"></i>
248
+ {error}
249
+ </div>
250
+ {/if}
251
+
252
+ {#if helpText && !error}
253
+ <div id="{id}-help" class="ap-form-help">
254
+ <i class="bi bi-info-circle"></i>
255
+ {helpText}
256
+ </div>
257
+ {/if}
258
+ </div>
259
+
260
+ <!-- Styles are in /src/lib/styles/forms.css -->
@@ -0,0 +1,68 @@
1
+ import type { Snippet } from "svelte";
2
+ import "../styles/forms.css";
3
+ interface FormFieldProps {
4
+ /** ID único do campo */
5
+ id: string;
6
+ /** Label do campo */
7
+ label: string;
8
+ /** Tipo do campo */
9
+ type?: 'text' | 'email' | 'password' | 'number' | 'tel' | 'url' | 'date' | 'time' | 'datetime-local' | 'textarea' | 'select' | 'search';
10
+ /** Valor do campo (bindable) */
11
+ value?: string | number;
12
+ /** Placeholder */
13
+ placeholder?: string;
14
+ /** Campo obrigatório */
15
+ required?: boolean;
16
+ /** Campo desabilitado */
17
+ disabled?: boolean;
18
+ /** Somente leitura */
19
+ readonly?: boolean;
20
+ /** Mensagem de erro */
21
+ error?: string;
22
+ /** Texto de ajuda */
23
+ helpText?: string;
24
+ /** Número de linhas (para textarea) */
25
+ rows?: number;
26
+ /** Opções (para select) */
27
+ options?: Array<{
28
+ value: string | number;
29
+ label: string;
30
+ }>;
31
+ /** Valor mínimo (para number/date) */
32
+ min?: number | string;
33
+ /** Valor máximo (para number/date) */
34
+ max?: number | string;
35
+ /** Step (para number) */
36
+ step?: number;
37
+ /** Pattern (regex) */
38
+ pattern?: string;
39
+ /** Autocomplete */
40
+ autocomplete?: HTMLInputElement['autocomplete'];
41
+ /** Tamanho mínimo (para text/password) */
42
+ minlength?: number;
43
+ /** Tamanho máximo (para text/password) */
44
+ maxlength?: number;
45
+ /** Callback de mudança */
46
+ onchange?: (value: any) => void;
47
+ /** Callback de input */
48
+ oninput?: (value: any) => void;
49
+ /** Callback de blur */
50
+ onblur?: () => void;
51
+ /** Callback de focus */
52
+ onfocus?: () => void;
53
+ /** Callback de keydown */
54
+ onkeydown?: (e: KeyboardEvent) => void;
55
+ /** Ícone do campo (exibido no label) */
56
+ icon?: Snippet;
57
+ /** Ícone dentro do input (à esquerda) */
58
+ inputIcon?: Snippet;
59
+ /** Addon à direita do input (ex: loading spinner) */
60
+ inputAddon?: Snippet;
61
+ /** Classes CSS adicionais */
62
+ class?: string;
63
+ /** Referência ao elemento input */
64
+ inputRef?: HTMLInputElement;
65
+ }
66
+ declare const FormField: import("svelte").Component<FormFieldProps, {}, "value" | "inputRef">;
67
+ type FormField = ReturnType<typeof FormField>;
68
+ export default FormField;