grav-svelte 0.0.98 → 0.1.0

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.
@@ -0,0 +1,171 @@
1
+ <script lang="ts">
2
+ import "../typography.css";
3
+
4
+ export let valueVar: string = "";
5
+ export let label: string;
6
+ export let disabled = false;
7
+ export let obligatory = false;
8
+ export let icon: string | null = null;
9
+ export let validation: boolean = false;
10
+
11
+ let validationMessage = "";
12
+ let isValid = true;
13
+
14
+ $: {
15
+ if (valueVar) {
16
+ // Email validation using standard regex
17
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
18
+
19
+ if (!emailRegex.test(valueVar)) {
20
+ validationMessage = "Invalid email format";
21
+ isValid = false;
22
+ } else {
23
+ validationMessage = "Email is valid";
24
+ isValid = true;
25
+ }
26
+ } else {
27
+ if (obligatory) {
28
+ validationMessage = "Email is required";
29
+ isValid = false;
30
+ } else {
31
+ validationMessage = "";
32
+ isValid = true;
33
+ }
34
+ }
35
+ }
36
+ </script>
37
+
38
+ <div class="input-container">
39
+ {#if icon}
40
+ <div class="icon-wrapper">
41
+ <i class="{icon} icon"></i>
42
+ </div>
43
+ {/if}
44
+ <div class="input-wrapper">
45
+ <input
46
+ {disabled}
47
+ type="email"
48
+ bind:value={valueVar}
49
+ placeholder=" "
50
+ class="input-field"
51
+ />
52
+
53
+ <label for={valueVar} class="input-label"
54
+ >{label}
55
+ {#if obligatory}
56
+ <span class="required-mark"> *</span>
57
+ {/if}</label
58
+ >
59
+ </div>
60
+ </div>
61
+ {#if validation && validationMessage}
62
+ <div
63
+ class="validation-message"
64
+ class:valid={isValid}
65
+ class:invalid={!isValid}
66
+ >
67
+ {validationMessage}
68
+ </div>
69
+ {/if}
70
+
71
+ <style>
72
+ .input-container {
73
+ display: flex;
74
+ align-items: center;
75
+ border: var(--grav-crud-input-border-width) solid
76
+ var(--grav-crud-color-neutral);
77
+ border-radius: 0.5rem;
78
+ padding-left: 0.5rem;
79
+ padding-right: 0.5rem;
80
+ padding-top: 0.2rem;
81
+ padding-bottom: 0.2rem;
82
+ margin-top: 1.95rem;
83
+ height: fit-content;
84
+ }
85
+
86
+ .icon-wrapper {
87
+ width: 1rem;
88
+ position: relative;
89
+ margin-right: 0.5rem;
90
+ }
91
+
92
+ .icon {
93
+ position: absolute;
94
+ top: -0.4rem;
95
+ left: 0.25rem;
96
+ color: var(--grav-crud-color-neutral);
97
+ }
98
+
99
+ .input-wrapper {
100
+ position: relative;
101
+ z-index: 0;
102
+ width: 100%;
103
+ }
104
+
105
+ .input-field {
106
+ display: block;
107
+ padding: 0.3rem;
108
+ width: 100%;
109
+ font-size: 1rem;
110
+ color: var(--grav-crud-color-neutral);
111
+ background: transparent;
112
+ appearance: none;
113
+ }
114
+
115
+ .input-field:focus {
116
+ outline: none;
117
+ }
118
+
119
+ .input-label {
120
+ position: absolute;
121
+ font-size: 1rem;
122
+ text-align: left;
123
+ color: var(--grav-crud-color-neutral);
124
+ transition: all 0.3s;
125
+ top: 0.25rem;
126
+ left: 0.25rem;
127
+ z-index: -10;
128
+ transform-origin: left;
129
+ }
130
+
131
+ .input-field:focus + .input-label,
132
+ .input-field:not(:placeholder-shown) + .input-label {
133
+ left: 0;
134
+ top: 0;
135
+ color: var(--grav-crud-color-neutral);
136
+ translate: -0.6rem -2.05rem;
137
+ scale: 1;
138
+ }
139
+
140
+ .input-field:placeholder-shown + .input-label {
141
+ transform: translateY(0) scale(1);
142
+ }
143
+
144
+ .required-mark {
145
+ color: #dc2626;
146
+ }
147
+
148
+ .no-margin {
149
+ margin-top: 0;
150
+ }
151
+
152
+ .no-margin .input-field:focus + .input-label,
153
+ .no-margin .input-field:not(:placeholder-shown) + .input-label {
154
+ translate: -0.6rem -1.4rem;
155
+ font-size: 0.7rem;
156
+ }
157
+
158
+ .validation-message {
159
+ font-size: 0.875rem;
160
+ margin-top: 0.25rem;
161
+ color: var(--grav-crud-color-neutral);
162
+ }
163
+
164
+ .validation-message.valid {
165
+ color: #10b981;
166
+ }
167
+
168
+ .validation-message.invalid {
169
+ color: #dc2626;
170
+ }
171
+ </style>
@@ -0,0 +1,22 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ import "../typography.css";
3
+ declare const __propDef: {
4
+ props: {
5
+ valueVar?: string;
6
+ label: string;
7
+ disabled?: boolean;
8
+ obligatory?: boolean;
9
+ icon?: string | null;
10
+ validation?: boolean;
11
+ };
12
+ events: {
13
+ [evt: string]: CustomEvent<any>;
14
+ };
15
+ slots: {};
16
+ };
17
+ export type InputFormMailProps = typeof __propDef.props;
18
+ export type InputFormMailEvents = typeof __propDef.events;
19
+ export type InputFormMailSlots = typeof __propDef.slots;
20
+ export default class InputFormMail extends SvelteComponentTyped<InputFormMailProps, InputFormMailEvents, InputFormMailSlots> {
21
+ }
22
+ export {};
@@ -0,0 +1,239 @@
1
+ <script lang="ts">
2
+ import "../typography.css";
3
+
4
+ interface CountryOption {
5
+ value: string;
6
+ label: string;
7
+ }
8
+
9
+ // Exported values - component always provides all 3
10
+ export let valueVar: string = ""; // Concatenated: "+528444612811"
11
+ export let dialCode: string = ""; // Dial code only: "+52"
12
+ export let phoneNumber: string = ""; // Phone number only: "8444612811"
13
+
14
+ // Common props
15
+ export let label: string;
16
+ export let disabled = false;
17
+ export let obligatory = false;
18
+ export let defaultDialCode: string = "+52";
19
+ export let validation: boolean = false;
20
+
21
+ // Internal state
22
+ let selectedDialCode: string = defaultDialCode;
23
+ let internalPhoneNumber: string = "";
24
+ let validationMessage = "";
25
+ let isValid = true;
26
+
27
+ // Country list with flag emojis (only flag + dial code)
28
+ const countries: CountryOption[] = [
29
+ { value: "+1", label: "🇺🇸 +1" },
30
+ { value: "+1", label: "🇨🇦 +1" },
31
+ { value: "+52", label: "🇲🇽 +52" },
32
+ { value: "+34", label: "🇪🇸 +34" },
33
+ { value: "+54", label: "🇦🇷 +54" },
34
+ { value: "+56", label: "🇨🇱 +56" },
35
+ { value: "+57", label: "🇨🇴 +57" },
36
+ { value: "+51", label: "🇵🇪 +51" },
37
+ { value: "+58", label: "🇻🇪 +58" },
38
+ { value: "+593", label: "🇪🇨 +593" },
39
+ { value: "+55", label: "🇧🇷 +55" },
40
+ { value: "+598", label: "🇺🇾 +598" },
41
+ { value: "+595", label: "🇵🇾 +595" },
42
+ { value: "+591", label: "🇧🇴 +591" },
43
+ { value: "+506", label: "🇨🇷 +506" },
44
+ { value: "+502", label: "🇬🇹 +502" },
45
+ { value: "+504", label: "🇭🇳 +504" },
46
+ { value: "+505", label: "🇳🇮 +505" },
47
+ { value: "+507", label: "🇵🇦 +507" },
48
+ { value: "+503", label: "🇸🇻 +503" },
49
+ { value: "+1", label: "🇩🇴 +1" },
50
+ { value: "+1", label: "🇵🇷 +1" },
51
+ { value: "+53", label: "🇨🇺 +53" },
52
+ { value: "+44", label: "🇬🇧 +44" },
53
+ { value: "+33", label: "🇫🇷 +33" },
54
+ { value: "+49", label: "🇩🇪 +49" },
55
+ { value: "+39", label: "🇮🇹 +39" },
56
+ { value: "+351", label: "🇵🇹 +351" },
57
+ { value: "+31", label: "🇳🇱 +31" },
58
+ { value: "+32", label: "🇧🇪 +32" },
59
+ ];
60
+
61
+ // Update all exported values reactively
62
+ $: {
63
+ const cleanNumber = internalPhoneNumber.replace(/\D/g, "");
64
+
65
+ // Update separate values
66
+ dialCode = selectedDialCode;
67
+ phoneNumber = cleanNumber;
68
+
69
+ // Update concatenated value
70
+ if (selectedDialCode && cleanNumber) {
71
+ valueVar = selectedDialCode + cleanNumber;
72
+ } else if (selectedDialCode) {
73
+ valueVar = selectedDialCode;
74
+ } else {
75
+ valueVar = "";
76
+ }
77
+
78
+ // Validation logic
79
+ if (validation) {
80
+ if (internalPhoneNumber && !/^\d+$/.test(cleanNumber)) {
81
+ validationMessage = "Phone number must contain only digits";
82
+ isValid = false;
83
+ } else if (cleanNumber && cleanNumber.length < 7) {
84
+ validationMessage = "Phone number must be at least 7 digits";
85
+ isValid = false;
86
+ } else if (cleanNumber && cleanNumber.length > 15) {
87
+ validationMessage = "Phone number must not exceed 15 digits";
88
+ isValid = false;
89
+ } else if (cleanNumber) {
90
+ validationMessage = "Phone number is valid";
91
+ isValid = true;
92
+ } else {
93
+ validationMessage = "";
94
+ isValid = true;
95
+ }
96
+ }
97
+ }
98
+
99
+ // Clean phone number input (only allow digits)
100
+ function handlePhoneInput(event: Event) {
101
+ const input = event.target as HTMLInputElement;
102
+ const cleaned = input.value.replace(/\D/g, "");
103
+ internalPhoneNumber = cleaned;
104
+ }
105
+ </script>
106
+
107
+ <div class="phone-container">
108
+ <label class="phone-label">
109
+ {label}
110
+ {#if obligatory}
111
+ <span class="required-mark"> *</span>
112
+ {/if}
113
+ </label>
114
+
115
+ <div class="phone-input-wrapper">
116
+ <div class="country-select-wrapper">
117
+ <select
118
+ bind:value={selectedDialCode}
119
+ {disabled}
120
+ class="country-select"
121
+ >
122
+ {#each countries as country}
123
+ <option value={country.value}>{country.label}</option>
124
+ {/each}
125
+ </select>
126
+ </div>
127
+
128
+ <div class="phone-number-wrapper">
129
+ <input
130
+ type="tel"
131
+ bind:value={internalPhoneNumber}
132
+ on:input={handlePhoneInput}
133
+ {disabled}
134
+ placeholder="Phone number"
135
+ class="phone-input"
136
+ />
137
+ </div>
138
+ </div>
139
+
140
+ {#if validation && validationMessage}
141
+ <div
142
+ class="validation-message"
143
+ class:valid={isValid}
144
+ class:invalid={!isValid}
145
+ >
146
+ {validationMessage}
147
+ </div>
148
+ {/if}
149
+ </div>
150
+
151
+ <style>
152
+ .phone-container {
153
+ display: flex;
154
+ flex-direction: column;
155
+ width: 100%;
156
+ margin-top: 1.95rem;
157
+ }
158
+
159
+ .phone-label {
160
+ font-size: 1rem;
161
+ color: var(--grav-crud-color-neutral);
162
+ margin-bottom: 0.25rem;
163
+ }
164
+
165
+ .required-mark {
166
+ color: #dc2626;
167
+ }
168
+
169
+ .phone-input-wrapper {
170
+ display: grid;
171
+ grid-template-columns: 80px 1fr;
172
+ border: var(--grav-crud-input-border-width) solid
173
+ var(--grav-crud-color-neutral);
174
+ border-radius: 0.5rem;
175
+ overflow: hidden;
176
+ }
177
+
178
+ .country-select-wrapper {
179
+ border-right: var(--grav-crud-input-border-width) solid
180
+ var(--grav-crud-color-neutral);
181
+ background-color: transparent;
182
+ }
183
+
184
+ .country-select {
185
+ width: 100%;
186
+ height: 100%;
187
+ padding: 0.5rem;
188
+ padding-right: 0.25rem;
189
+ font-size: 1rem;
190
+ color: var(--grav-crud-color-neutral);
191
+ background-color: transparent;
192
+ border: none;
193
+ outline: none;
194
+ cursor: pointer;
195
+ appearance: none;
196
+ -webkit-appearance: none;
197
+ -moz-appearance: none;
198
+ }
199
+
200
+ .country-select:disabled {
201
+ cursor: not-allowed;
202
+ opacity: 0.6;
203
+ }
204
+
205
+ .phone-number-wrapper {
206
+ display: flex;
207
+ align-items: center;
208
+ padding: 0 0.5rem;
209
+ }
210
+
211
+ .phone-input {
212
+ width: 100%;
213
+ padding: 0.5rem;
214
+ font-size: 1rem;
215
+ color: var(--grav-crud-color-neutral);
216
+ background: transparent;
217
+ border: none;
218
+ outline: none;
219
+ }
220
+
221
+ .phone-input::placeholder {
222
+ color: var(--grav-crud-color-neutral);
223
+ opacity: 0.5;
224
+ }
225
+
226
+ .validation-message {
227
+ font-size: 0.875rem;
228
+ margin-top: 0.25rem;
229
+ color: var(--grav-crud-color-neutral);
230
+ }
231
+
232
+ .validation-message.valid {
233
+ color: #10b981;
234
+ }
235
+
236
+ .validation-message.invalid {
237
+ color: #dc2626;
238
+ }
239
+ </style>
@@ -0,0 +1,24 @@
1
+ import { SvelteComponentTyped } from "svelte";
2
+ import "../typography.css";
3
+ declare const __propDef: {
4
+ props: {
5
+ valueVar?: string;
6
+ dialCode?: string;
7
+ phoneNumber?: string;
8
+ label: string;
9
+ disabled?: boolean;
10
+ obligatory?: boolean;
11
+ defaultDialCode?: string;
12
+ validation?: boolean;
13
+ };
14
+ events: {
15
+ [evt: string]: CustomEvent<any>;
16
+ };
17
+ slots: {};
18
+ };
19
+ export type InputFormPhoneProps = typeof __propDef.props;
20
+ export type InputFormPhoneEvents = typeof __propDef.events;
21
+ export type InputFormPhoneSlots = typeof __propDef.slots;
22
+ export default class InputFormPhone extends SvelteComponentTyped<InputFormPhoneProps, InputFormPhoneEvents, InputFormPhoneSlots> {
23
+ }
24
+ export {};
@@ -9,4 +9,6 @@ export { default as InputFormNumber } from './InputFormNumber.svelte';
9
9
  export { default as InputFormSelect } from './InputFormSelect.svelte';
10
10
  export { default as InputFormText } from './InputFormText.svelte';
11
11
  export { default as InputFormPassword } from './InputFormPassword.svelte';
12
+ export { default as InputFormMail } from './InputFormMail.svelte';
13
+ export { default as InputFormPhone } from './InputFormPhone.svelte';
12
14
  export { default as InputFormTextArea } from './InputFormTextArea.svelte';
@@ -9,4 +9,6 @@ export { default as InputFormNumber } from './InputFormNumber.svelte';
9
9
  export { default as InputFormSelect } from './InputFormSelect.svelte';
10
10
  export { default as InputFormText } from './InputFormText.svelte';
11
11
  export { default as InputFormPassword } from './InputFormPassword.svelte';
12
+ export { default as InputFormMail } from './InputFormMail.svelte';
13
+ export { default as InputFormPhone } from './InputFormPhone.svelte';
12
14
  export { default as InputFormTextArea } from './InputFormTextArea.svelte';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grav-svelte",
3
- "version": "0.0.98",
3
+ "version": "0.1.0",
4
4
  "description": "A collection of Svelte components",
5
5
  "license": "MIT",
6
6
  "scripts": {