@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,473 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { onDestroy } from "svelte";
4
+ import { lockBodyScroll, unlockBodyScroll } from "../utils/scrollLock.js";
5
+
6
+ type HeaderVariant = "default" | "primary" | "secondary" | "success" | "warning" | "danger" | "info" | "gradient";
7
+
8
+ type BodyPadding = "none" | "sm" | "md" | "lg";
9
+
10
+ interface ModalProps {
11
+ /** Modal title displayed in the header */
12
+ title: string;
13
+ /** Controls modal visibility */
14
+ show: boolean;
15
+ /** Callback function when modal is closed */
16
+ onClose: () => void;
17
+ /** Modal content */
18
+ children?: Snippet;
19
+ /** Optional footer snippet. When provided, Modal renders a styled footer area. */
20
+ footer?: Snippet;
21
+ /** Additional CSS classes */
22
+ class?: string;
23
+ /** Modal size variant */
24
+ size?: "sm" | "md" | "lg" | "xl";
25
+ /**
26
+ * Padding interno aplicado ao wrapper `.ap-modal-body`.
27
+ * - `none`: 0 (conteúdo edge-to-edge, ex.: tabelas, listas)
28
+ * - `sm`: 0.75rem
29
+ * - `md` (default): 1rem
30
+ * - `lg`: 1.25rem 1.5rem (conteúdo mais "arejado")
31
+ */
32
+ bodyPadding?: BodyPadding;
33
+ /** Whether clicking the backdrop closes the modal (default: true) */
34
+ closeOnBackdropClick?: boolean;
35
+ /** Header color variant */
36
+ headerVariant?: HeaderVariant;
37
+ /** Custom header snippet (replaces default header) */
38
+ customHeader?: Snippet;
39
+ /** Custom modal z-index when this instance must stack above other overlays */
40
+ zIndex?: number;
41
+ /** Custom backdrop z-index for this modal instance */
42
+ backdropZIndex?: number;
43
+ /** Centra verticalmente o diálogo no viewport (Bootstrap `modal-dialog-centered`). */
44
+ centered?: boolean;
45
+ }
46
+
47
+ let {
48
+ title,
49
+ show = $bindable(false),
50
+ onClose,
51
+ children,
52
+ footer,
53
+ class: additionalClasses = "",
54
+ size = "md",
55
+ bodyPadding = "md",
56
+ closeOnBackdropClick = false,
57
+ headerVariant = "default",
58
+ customHeader,
59
+ zIndex,
60
+ backdropZIndex,
61
+ centered = false,
62
+ }: ModalProps = $props();
63
+
64
+ let modalElement = $state<HTMLDivElement | null>(null);
65
+ let isOpen = $state(false);
66
+ let hasScrollLock = false;
67
+
68
+ function acquireScrollLock() {
69
+ if (hasScrollLock) return;
70
+ lockBodyScroll();
71
+ hasScrollLock = true;
72
+ }
73
+
74
+ function releaseScrollLock() {
75
+ if (!hasScrollLock) return;
76
+ unlockBodyScroll();
77
+ hasScrollLock = false;
78
+ }
79
+
80
+ // Handle show/hide transitions
81
+ $effect(() => {
82
+ if (show) {
83
+ const timeout = setTimeout(() => {
84
+ isOpen = true;
85
+ acquireScrollLock();
86
+ }, 10);
87
+
88
+ return () => clearTimeout(timeout);
89
+ }
90
+
91
+ isOpen = false;
92
+ releaseScrollLock();
93
+ });
94
+
95
+ onDestroy(() => {
96
+ releaseScrollLock();
97
+ });
98
+
99
+ // Handle escape key
100
+ function handleKeydown(event: KeyboardEvent) {
101
+ if (event.key === "Escape" && show) {
102
+ onClose();
103
+ }
104
+ }
105
+
106
+ // Handle backdrop click
107
+ function handleBackdropClick(event: MouseEvent) {
108
+ if (closeOnBackdropClick && event.target === modalElement) {
109
+ onClose();
110
+ }
111
+ }
112
+
113
+ const sizeClasses: Record<string, string> = {
114
+ sm: "ap-modal-sm",
115
+ md: "",
116
+ lg: "ap-modal-lg",
117
+ xl: "ap-modal-xl",
118
+ };
119
+
120
+ const modalDialogClass = $derived([
121
+ "ap-modal-dialog",
122
+ sizeClasses[size],
123
+ centered ? "ap-modal-dialog-centered" : "",
124
+ additionalClasses
125
+ ].filter(Boolean).join(" "));
126
+ </script>
127
+
128
+ <svelte:window onkeydown={handleKeydown} />
129
+
130
+ {#if show}
131
+ <!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
132
+ <!-- svelte-ignore a11y_click_events_have_key_events -->
133
+ <div
134
+ bind:this={modalElement}
135
+ class="ap-modal"
136
+ class:show={isOpen}
137
+ class:fade={true}
138
+ style={`display: block;${zIndex !== undefined ? ` --z-modal: ${zIndex};` : ""}`}
139
+ tabindex="-1"
140
+ role="dialog"
141
+ aria-modal="true"
142
+ aria-labelledby="modal-title"
143
+ onclick={handleBackdropClick}
144
+ >
145
+ <div class={modalDialogClass} role="document">
146
+ <div class="ap-modal-content">
147
+ {#if customHeader}
148
+ {@render customHeader()}
149
+ {:else}
150
+ <div
151
+ class="ap-modal-header"
152
+ class:ap-modal-header-primary={headerVariant === "primary"}
153
+ class:ap-modal-header-secondary={headerVariant === "secondary"}
154
+ class:ap-modal-header-success={headerVariant === "success"}
155
+ class:ap-modal-header-warning={headerVariant === "warning"}
156
+ class:ap-modal-header-danger={headerVariant === "danger"}
157
+ class:ap-modal-header-info={headerVariant === "info"}
158
+ class:ap-modal-header-gradient={headerVariant === "gradient"}
159
+ >
160
+ <h5 class="ap-modal-title" id="modal-title">{title}</h5>
161
+ <button
162
+ type="button"
163
+ class="ap-modal-close"
164
+ class:ap-modal-close-light={headerVariant && headerVariant !== "default"}
165
+ aria-label="Close"
166
+ onclick={onClose}
167
+ >
168
+ <i class="bi bi-x-lg"></i>
169
+ </button>
170
+ </div>
171
+ {/if}
172
+
173
+ {#if children}
174
+ <div class="ap-modal-body ap-modal-body-padding-{bodyPadding}">
175
+ {@render children()}
176
+ </div>
177
+ {/if}
178
+
179
+ {#if footer}
180
+ <div class="ap-modal-footer">
181
+ {@render footer()}
182
+ </div>
183
+ {/if}
184
+ </div>
185
+ </div>
186
+ </div>
187
+
188
+ <div
189
+ class="ap-modal-backdrop"
190
+ class:show={isOpen}
191
+ class:fade={true}
192
+ style={backdropZIndex !== undefined ? `--z-modal-backdrop: ${backdropZIndex};` : undefined}
193
+ ></div>
194
+ {/if}
195
+
196
+ <style>
197
+ /* ============================================
198
+ MODAL - AgendaPet Design System
199
+ ============================================ */
200
+ .ap-modal {
201
+ position: fixed;
202
+ top: 0;
203
+ left: 0;
204
+ z-index: var(--z-modal, 1050);
205
+ width: 100%;
206
+ height: 100%;
207
+ overflow-x: hidden;
208
+ overflow-y: auto;
209
+ outline: 0;
210
+ transition: opacity var(--transition-base);
211
+ }
212
+
213
+ .ap-modal-backdrop {
214
+ position: fixed;
215
+ top: 0;
216
+ left: 0;
217
+ z-index: var(--z-modal-backdrop, 1040);
218
+ width: 100vw;
219
+ height: 100vh;
220
+ background-color: rgba(0, 0, 0, 0.5);
221
+ transition: opacity var(--transition-base);
222
+ }
223
+
224
+ .ap-modal.fade {
225
+ opacity: 0;
226
+ }
227
+
228
+ .ap-modal.fade.show {
229
+ opacity: 1;
230
+ }
231
+
232
+ .ap-modal-backdrop.fade {
233
+ opacity: 0;
234
+ }
235
+
236
+ .ap-modal-backdrop.fade.show {
237
+ opacity: 1;
238
+ }
239
+
240
+ /* ===== DIALOG ===== */
241
+ .ap-modal-dialog {
242
+ position: relative;
243
+ width: auto;
244
+ margin: 0.5rem;
245
+ pointer-events: none;
246
+ transition: transform var(--transition-base);
247
+ }
248
+
249
+ .ap-modal-dialog-centered {
250
+ display: flex;
251
+ align-items: center;
252
+ min-height: calc(100% - 1rem);
253
+ }
254
+
255
+ @media (min-width: 576px) {
256
+ .ap-modal-dialog-centered {
257
+ min-height: calc(100% - 3.5rem);
258
+ }
259
+ }
260
+
261
+ .ap-modal.fade .ap-modal-dialog {
262
+ transform: translate(0, -20px);
263
+ }
264
+
265
+ .ap-modal.show .ap-modal-dialog {
266
+ transform: translate(0, 0);
267
+ }
268
+
269
+ /* ===== SIZES ===== */
270
+ @media (min-width: 576px) {
271
+ .ap-modal-dialog {
272
+ max-width: 500px;
273
+ margin: 1.75rem auto;
274
+ }
275
+
276
+ .ap-modal-sm {
277
+ max-width: 360px;
278
+ }
279
+
280
+ .ap-modal-lg {
281
+ max-width: 800px;
282
+ }
283
+
284
+ .ap-modal-xl {
285
+ max-width: 1140px;
286
+ }
287
+ }
288
+
289
+ /* ===== CONTENT ===== */
290
+ .ap-modal-content {
291
+ position: relative;
292
+ display: flex;
293
+ flex-direction: column;
294
+ width: 100%;
295
+ pointer-events: auto;
296
+ background-color: var(--color-bg-surface);
297
+ background-clip: padding-box;
298
+ border: none;
299
+ border-radius: var(--radius-xl);
300
+ box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
301
+ outline: 0;
302
+ max-height: calc(100vh - 1rem);
303
+ overflow: hidden;
304
+ }
305
+
306
+ @media (min-width: 576px) {
307
+ .ap-modal-content {
308
+ max-height: calc(100vh - 3.5rem);
309
+ }
310
+ }
311
+
312
+ /* ===== HEADER ===== */
313
+ .ap-modal-header {
314
+ display: flex;
315
+ align-items: center;
316
+ justify-content: space-between;
317
+ padding: 1rem 1.25rem;
318
+ border-bottom: 1px solid var(--color-border-subtle);
319
+ background-color: var(--color-bg-surface);
320
+ }
321
+
322
+ .ap-modal-title {
323
+ font-size: var(--font-size-base);
324
+ font-weight: var(--font-weight-medium);
325
+ color: var(--color-text);
326
+ margin: 0;
327
+ line-height: 1.4;
328
+ }
329
+
330
+ .ap-modal-close {
331
+ display: flex;
332
+ align-items: center;
333
+ justify-content: center;
334
+ width: 32px;
335
+ height: 32px;
336
+ padding: 0;
337
+ background: transparent;
338
+ border: none;
339
+ border-radius: var(--radius-sm);
340
+ cursor: pointer;
341
+ opacity: 0.6;
342
+ transition: opacity var(--transition-fast);
343
+ color: var(--color-text);
344
+ }
345
+
346
+ .ap-modal-close:hover {
347
+ opacity: 1;
348
+ }
349
+
350
+ .ap-modal-close:focus-visible {
351
+ outline: 2px solid var(--color-primary);
352
+ outline-offset: 2px;
353
+ }
354
+
355
+ .ap-modal-close-light {
356
+ color: white;
357
+ opacity: 0.8;
358
+ }
359
+
360
+ .ap-modal-close-light:hover {
361
+ opacity: 1;
362
+ }
363
+
364
+ /* ===== HEADER VARIANTS (Gradient 135deg) ===== */
365
+ .ap-modal-header-primary {
366
+ background: linear-gradient(135deg, #7B3FF2 0%, #5A27D6 100%);
367
+ color: white;
368
+ border-bottom: none;
369
+ }
370
+
371
+ .ap-modal-header-primary .ap-modal-title {
372
+ color: white;
373
+ }
374
+
375
+ .ap-modal-header-secondary {
376
+ background: linear-gradient(135deg, var(--color-secondary) 0%, var(--color-secondary-hover) 100%);
377
+ color: white;
378
+ border-bottom: none;
379
+ }
380
+
381
+ .ap-modal-header-secondary .ap-modal-title {
382
+ color: white;
383
+ }
384
+
385
+ .ap-modal-header-success {
386
+ background: linear-gradient(135deg, #18B77E 0%, #128A5E 100%);
387
+ color: white;
388
+ border-bottom: none;
389
+ }
390
+
391
+ .ap-modal-header-success .ap-modal-title {
392
+ color: white;
393
+ }
394
+
395
+ .ap-modal-header-warning {
396
+ background: linear-gradient(135deg, #9A7500 0%, #7A5C00 100%);
397
+ color: white;
398
+ border-bottom: none;
399
+ }
400
+
401
+ .ap-modal-header-warning .ap-modal-title {
402
+ color: white;
403
+ }
404
+
405
+ .ap-modal-header-danger {
406
+ background: linear-gradient(135deg, #E4475A 0%, #C33545 100%);
407
+ color: white;
408
+ border-bottom: none;
409
+ }
410
+
411
+ .ap-modal-header-danger .ap-modal-title {
412
+ color: white;
413
+ }
414
+
415
+ .ap-modal-header-info {
416
+ background: linear-gradient(135deg, #18A7D6 0%, #1287AD 100%);
417
+ color: white;
418
+ border-bottom: none;
419
+ }
420
+
421
+ .ap-modal-header-info .ap-modal-title {
422
+ color: white;
423
+ }
424
+
425
+ .ap-modal-header-gradient {
426
+ background: linear-gradient(135deg, #15B9FF 0%, #7B3FF2 50%, #E414FF 100%);
427
+ color: white;
428
+ border-bottom: none;
429
+ }
430
+
431
+ .ap-modal-header-gradient .ap-modal-title {
432
+ color: white;
433
+ }
434
+
435
+ /* ===== BODY (Global for nested content) ===== */
436
+ :global(.ap-modal-body) {
437
+ flex: 1 1 auto;
438
+ overflow-y: auto;
439
+ }
440
+
441
+ /* Padding variants applied pela prop bodyPadding. Globais para cobrir
442
+ também consumidores que aninham o próprio conteúdo dentro do wrapper. */
443
+ :global(.ap-modal-body-padding-none) {
444
+ padding: 0;
445
+ }
446
+ :global(.ap-modal-body-padding-sm) {
447
+ padding: 0.75rem;
448
+ }
449
+ :global(.ap-modal-body-padding-md) {
450
+ padding: 1rem;
451
+ }
452
+ :global(.ap-modal-body-padding-lg) {
453
+ padding: 1.25rem 1.5rem;
454
+ }
455
+
456
+ :global(.ap-modal-body p) {
457
+ color: var(--color-text-muted);
458
+ font-size: var(--font-size-sm);
459
+ line-height: 1.5;
460
+ }
461
+
462
+ /* ===== FOOTER =====
463
+ Aplicado pelo snippet `footer` ao wrapper `.ap-modal-footer`.
464
+ Consumidores devem usar `{#snippet footer()}` em vez de `<div class="modal-footer">`. */
465
+ :global(.ap-modal-footer) {
466
+ display: flex;
467
+ gap: 0.625rem;
468
+ justify-content: flex-end;
469
+ padding: 1rem 1.5rem;
470
+ background: var(--color-bg-surface-2);
471
+ border-top: 1px solid var(--color-border-subtle);
472
+ }
473
+ </style>
@@ -0,0 +1,42 @@
1
+ import type { Snippet } from "svelte";
2
+ type HeaderVariant = "default" | "primary" | "secondary" | "success" | "warning" | "danger" | "info" | "gradient";
3
+ type BodyPadding = "none" | "sm" | "md" | "lg";
4
+ interface ModalProps {
5
+ /** Modal title displayed in the header */
6
+ title: string;
7
+ /** Controls modal visibility */
8
+ show: boolean;
9
+ /** Callback function when modal is closed */
10
+ onClose: () => void;
11
+ /** Modal content */
12
+ children?: Snippet;
13
+ /** Optional footer snippet. When provided, Modal renders a styled footer area. */
14
+ footer?: Snippet;
15
+ /** Additional CSS classes */
16
+ class?: string;
17
+ /** Modal size variant */
18
+ size?: "sm" | "md" | "lg" | "xl";
19
+ /**
20
+ * Padding interno aplicado ao wrapper `.ap-modal-body`.
21
+ * - `none`: 0 (conteúdo edge-to-edge, ex.: tabelas, listas)
22
+ * - `sm`: 0.75rem
23
+ * - `md` (default): 1rem
24
+ * - `lg`: 1.25rem 1.5rem (conteúdo mais "arejado")
25
+ */
26
+ bodyPadding?: BodyPadding;
27
+ /** Whether clicking the backdrop closes the modal (default: true) */
28
+ closeOnBackdropClick?: boolean;
29
+ /** Header color variant */
30
+ headerVariant?: HeaderVariant;
31
+ /** Custom header snippet (replaces default header) */
32
+ customHeader?: Snippet;
33
+ /** Custom modal z-index when this instance must stack above other overlays */
34
+ zIndex?: number;
35
+ /** Custom backdrop z-index for this modal instance */
36
+ backdropZIndex?: number;
37
+ /** Centra verticalmente o diálogo no viewport (Bootstrap `modal-dialog-centered`). */
38
+ centered?: boolean;
39
+ }
40
+ declare const Modal: import("svelte").Component<ModalProps, {}, "show">;
41
+ type Modal = ReturnType<typeof Modal>;
42
+ export default Modal;