@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,170 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from "svelte";
3
+ import { minutesToHoursAndMinutes, hoursAndMinutesToMinutes } from "../utils/duration.js";
4
+ import "../styles/forms.css";
5
+
6
+ interface DurationInputProps {
7
+ /** ID base do campo (usado em horas/minutos e no label) */
8
+ id: string;
9
+ /** Valor total em minutos (bindable). null = vazio */
10
+ value?: number | null;
11
+ /** Label opcional (omitir quando usado inline, ex.: célula de tabela) */
12
+ label?: string;
13
+ /** Texto de ajuda; se omitido, mostra a duração formatada (ex.: "= 1h 20min") */
14
+ helpText?: string;
15
+ /** Campo obrigatório (marca o label) */
16
+ required?: boolean;
17
+ /** Campo desabilitado */
18
+ disabled?: boolean;
19
+ /** Tamanho: 'sm' para células de tabela / ajustes inline */
20
+ size?: "sm" | "md";
21
+ /** Callback com o total em minutos (null quando vazio) */
22
+ onchange?: (minutes: number | null) => void;
23
+ /** Classes CSS adicionais no contentor */
24
+ class?: string;
25
+ /** Ícone exibido no label */
26
+ icon?: Snippet;
27
+ }
28
+
29
+ let {
30
+ id,
31
+ value = $bindable<number | null>(null),
32
+ label,
33
+ helpText,
34
+ required = false,
35
+ disabled = false,
36
+ size = "md",
37
+ onchange,
38
+ class: additionalClasses = "",
39
+ icon,
40
+ }: DurationInputProps = $props();
41
+
42
+ // Local field state, kept in sync with `value`.
43
+ let hours = $state<number | null>(null);
44
+ let minutes = $state<number | null>(null);
45
+
46
+ // Sync local fields whenever `value` changes from the outside (parent reset,
47
+ // async load). The guard avoids clobbering the user's input with our own
48
+ // round-tripped value while typing.
49
+ $effect(() => {
50
+ const combined = hoursAndMinutesToMinutes(hours, minutes);
51
+ if (combined !== (value ?? null)) {
52
+ const parts = minutesToHoursAndMinutes(value);
53
+ hours = parts.hours;
54
+ minutes = parts.minutes;
55
+ }
56
+ });
57
+
58
+ function emit() {
59
+ const total = hoursAndMinutesToMinutes(hours, minutes);
60
+ value = total;
61
+ onchange?.(total);
62
+ }
63
+
64
+ function onHoursInput(event: Event) {
65
+ const raw = (event.target as HTMLInputElement).value;
66
+ hours = raw === "" ? null : Math.max(0, Math.floor(Number(raw)));
67
+ emit();
68
+ }
69
+
70
+ function onMinutesInput(event: Event) {
71
+ const raw = (event.target as HTMLInputElement).value;
72
+ minutes = raw === "" ? null : Math.max(0, Math.min(59, Math.floor(Number(raw))));
73
+ emit();
74
+ }
75
+
76
+ const containerClass = $derived(["ap-form-field", additionalClasses].filter(Boolean).join(" "));
77
+ </script>
78
+
79
+ <div class={containerClass}>
80
+ {#if label}
81
+ <label for={`${id}-hours`} class="ap-form-label">
82
+ {#if icon}
83
+ <span class="ap-form-label-icon">{@render icon()}</span>
84
+ {/if}
85
+ {label}
86
+ {#if required}
87
+ <span class="ap-form-required" aria-label="obrigatório">*</span>
88
+ {/if}
89
+ </label>
90
+ {/if}
91
+
92
+ <div class="duration-input" class:duration-input-sm={size === "sm"}>
93
+ <div class="duration-field">
94
+ <input
95
+ id={`${id}-hours`}
96
+ type="number"
97
+ class="ap-form-control"
98
+ min="0"
99
+ step="1"
100
+ inputmode="numeric"
101
+ placeholder="0"
102
+ value={hours ?? ""}
103
+ oninput={onHoursInput}
104
+ {disabled}
105
+ aria-label="horas"
106
+ />
107
+ <span class="duration-unit">h</span>
108
+ </div>
109
+ <div class="duration-field">
110
+ <input
111
+ id={`${id}-minutes`}
112
+ type="number"
113
+ class="ap-form-control"
114
+ min="0"
115
+ max="59"
116
+ step="5"
117
+ inputmode="numeric"
118
+ placeholder="00"
119
+ value={minutes ?? ""}
120
+ oninput={onMinutesInput}
121
+ {disabled}
122
+ aria-label="minutos"
123
+ />
124
+ <span class="duration-unit">min</span>
125
+ </div>
126
+ </div>
127
+
128
+ {#if helpText}
129
+ <div class="ap-form-help"><i class="bi bi-info-circle"></i> {helpText}</div>
130
+ {/if}
131
+ </div>
132
+
133
+ <style>
134
+ .duration-input {
135
+ display: flex;
136
+ align-items: center;
137
+ gap: 0.75rem;
138
+ }
139
+
140
+ .duration-field {
141
+ display: flex;
142
+ align-items: center;
143
+ gap: 0.35rem;
144
+ }
145
+
146
+ .duration-field :global(.ap-form-control) {
147
+ width: 5rem;
148
+ text-align: center;
149
+ }
150
+
151
+ .duration-unit {
152
+ color: var(--ap-text-muted, #6c757d);
153
+ font-size: 0.9rem;
154
+ }
155
+
156
+ .duration-input-sm {
157
+ gap: 0.5rem;
158
+ }
159
+
160
+ .duration-input-sm .duration-field :global(.ap-form-control) {
161
+ width: 3.75rem;
162
+ padding-top: 0.25rem;
163
+ padding-bottom: 0.25rem;
164
+ font-size: 0.85rem;
165
+ }
166
+
167
+ .duration-input-sm .duration-unit {
168
+ font-size: 0.8rem;
169
+ }
170
+ </style>
@@ -0,0 +1,27 @@
1
+ import type { Snippet } from "svelte";
2
+ import "../styles/forms.css";
3
+ interface DurationInputProps {
4
+ /** ID base do campo (usado em horas/minutos e no label) */
5
+ id: string;
6
+ /** Valor total em minutos (bindable). null = vazio */
7
+ value?: number | null;
8
+ /** Label opcional (omitir quando usado inline, ex.: célula de tabela) */
9
+ label?: string;
10
+ /** Texto de ajuda; se omitido, mostra a duração formatada (ex.: "= 1h 20min") */
11
+ helpText?: string;
12
+ /** Campo obrigatório (marca o label) */
13
+ required?: boolean;
14
+ /** Campo desabilitado */
15
+ disabled?: boolean;
16
+ /** Tamanho: 'sm' para células de tabela / ajustes inline */
17
+ size?: "sm" | "md";
18
+ /** Callback com o total em minutos (null quando vazio) */
19
+ onchange?: (minutes: number | null) => void;
20
+ /** Classes CSS adicionais no contentor */
21
+ class?: string;
22
+ /** Ícone exibido no label */
23
+ icon?: Snippet;
24
+ }
25
+ declare const DurationInput: import("svelte").Component<DurationInputProps, {}, "value">;
26
+ type DurationInput = ReturnType<typeof DurationInput>;
27
+ export default DurationInput;