@lub-crm/forms 1.0.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.
- package/README.md +298 -0
- package/dist/lub-forms.css +1 -0
- package/dist/lub-forms.es.js +5848 -0
- package/dist/lub-forms.es.js.map +1 -0
- package/dist/lub-forms.standalone.js +10 -0
- package/dist/lub-forms.standalone.js.map +1 -0
- package/dist/lub-forms.umd.js +227 -0
- package/dist/lub-forms.umd.js.map +1 -0
- package/package.json +68 -0
- package/src/api/client.ts +115 -0
- package/src/api/index.ts +2 -0
- package/src/api/types.ts +202 -0
- package/src/core/FormProvider.tsx +228 -0
- package/src/core/FormRenderer.tsx +134 -0
- package/src/core/LubForm.tsx +476 -0
- package/src/core/StepManager.tsx +199 -0
- package/src/core/index.ts +4 -0
- package/src/embed.ts +188 -0
- package/src/fields/CheckboxField.tsx +62 -0
- package/src/fields/CheckboxGroupField.tsx +57 -0
- package/src/fields/CountryField.tsx +43 -0
- package/src/fields/DateField.tsx +33 -0
- package/src/fields/DateTimeField.tsx +33 -0
- package/src/fields/DividerField.tsx +16 -0
- package/src/fields/FieldWrapper.tsx +60 -0
- package/src/fields/FileField.tsx +45 -0
- package/src/fields/HiddenField.tsx +18 -0
- package/src/fields/HtmlField.tsx +17 -0
- package/src/fields/NumberField.tsx +39 -0
- package/src/fields/RadioField.tsx +57 -0
- package/src/fields/RecaptchaField.tsx +137 -0
- package/src/fields/SelectField.tsx +49 -0
- package/src/fields/StateField.tsx +84 -0
- package/src/fields/TextField.tsx +51 -0
- package/src/fields/TextareaField.tsx +37 -0
- package/src/fields/TimeField.tsx +33 -0
- package/src/fields/index.ts +84 -0
- package/src/hooks/index.ts +4 -0
- package/src/hooks/useConditionalLogic.ts +59 -0
- package/src/hooks/useFormApi.ts +118 -0
- package/src/hooks/useFormDesign.ts +48 -0
- package/src/hooks/useMultiStep.ts +98 -0
- package/src/index.ts +101 -0
- package/src/main.tsx +40 -0
- package/src/styles/index.css +707 -0
- package/src/utils/cn.ts +6 -0
- package/src/utils/countries.ts +163 -0
- package/src/utils/css-variables.ts +63 -0
- package/src/utils/index.ts +3 -0
- package/src/validation/conditional.ts +170 -0
- package/src/validation/index.ts +2 -0
- package/src/validation/schema-builder.ts +327 -0
package/README.md
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# @lub/forms
|
|
2
|
+
|
|
3
|
+
Embeddable form library for Lub CRM. Render dynamic forms that integrate with Lub CRM for lead capture and data collection.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Multiple deployment options: React component, UMD script, or standalone bundle
|
|
8
|
+
- Multi-step form support with progress indicators
|
|
9
|
+
- Conditional field visibility and requirements
|
|
10
|
+
- Dynamic validation with Zod schemas
|
|
11
|
+
- CRM field mapping for automatic lead creation
|
|
12
|
+
- UTM parameter tracking
|
|
13
|
+
- Customizable styling via CSS variables
|
|
14
|
+
- 17+ field types including country/state selectors
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
### NPM Package (React Apps)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install @lub/forms
|
|
22
|
+
# or
|
|
23
|
+
bun add @lub/forms
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Script Tag (Standalone)
|
|
27
|
+
|
|
28
|
+
No build step required. Includes React and all dependencies:
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<script src="https://forms.lub.com/v1/lub-forms.standalone.js"></script>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Script Tag (UMD)
|
|
35
|
+
|
|
36
|
+
For pages that already have React loaded:
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<script src="https://unpkg.com/react@19/umd/react.production.min.js"></script>
|
|
40
|
+
<script src="https://unpkg.com/react-dom@19/umd/react-dom.production.min.js"></script>
|
|
41
|
+
<script src="https://forms.lub.com/v1/lub-forms.umd.js"></script>
|
|
42
|
+
<link rel="stylesheet" href="https://forms.lub.com/v1/lub-forms.css" />
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
### React Component
|
|
48
|
+
|
|
49
|
+
```tsx
|
|
50
|
+
import { LubForm } from "@lub/forms";
|
|
51
|
+
import "@lub/forms/styles";
|
|
52
|
+
|
|
53
|
+
function ContactPage() {
|
|
54
|
+
return (
|
|
55
|
+
<LubForm
|
|
56
|
+
formId="abc123"
|
|
57
|
+
baseUrl="https://api.lub.com"
|
|
58
|
+
onSuccess={(data) => {
|
|
59
|
+
console.log("Submission ID:", data.submission_id);
|
|
60
|
+
window.location.href = "/thank-you";
|
|
61
|
+
}}
|
|
62
|
+
onError={(error) => {
|
|
63
|
+
console.error("Submission failed:", error.message);
|
|
64
|
+
}}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Script Tag (Auto-mount)
|
|
71
|
+
|
|
72
|
+
Forms automatically mount to elements with `data-lub-form-id`:
|
|
73
|
+
|
|
74
|
+
```html
|
|
75
|
+
<div
|
|
76
|
+
data-lub-form-id="abc123"
|
|
77
|
+
data-lub-base-url="https://api.lub.com"
|
|
78
|
+
data-lub-class="my-custom-class"
|
|
79
|
+
data-lub-on-success="handleSuccess"
|
|
80
|
+
data-lub-on-error="handleError"
|
|
81
|
+
></div>
|
|
82
|
+
|
|
83
|
+
<script>
|
|
84
|
+
function handleSuccess(data) {
|
|
85
|
+
window.location.href = "/thank-you";
|
|
86
|
+
}
|
|
87
|
+
function handleError(error) {
|
|
88
|
+
alert("Something went wrong");
|
|
89
|
+
}
|
|
90
|
+
</script>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Script Tag (Manual Render)
|
|
94
|
+
|
|
95
|
+
```html
|
|
96
|
+
<div id="my-form"></div>
|
|
97
|
+
|
|
98
|
+
<script>
|
|
99
|
+
LubForms.render("abc123", "my-form", {
|
|
100
|
+
baseUrl: "https://api.lub.com",
|
|
101
|
+
onSuccess: (data) => {
|
|
102
|
+
console.log("Success!", data);
|
|
103
|
+
},
|
|
104
|
+
onError: (err) => {
|
|
105
|
+
console.error("Error:", err);
|
|
106
|
+
},
|
|
107
|
+
designOverrides: {
|
|
108
|
+
primary_color: "#8b5cf6",
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
</script>
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Props / Options
|
|
115
|
+
|
|
116
|
+
| Prop | Type | Description |
|
|
117
|
+
| ------------------- | ------------------------------------------ | ------------------------------------------- |
|
|
118
|
+
| `formId` | `string` | Form ID from Lub CRM (required) |
|
|
119
|
+
| `baseUrl` | `string` | API base URL |
|
|
120
|
+
| `onSuccess` | `(data: SubmitFormResponse) => void` | Called on successful submission |
|
|
121
|
+
| `onError` | `(error: ApiError) => void` | Called on submission error |
|
|
122
|
+
| `onValidationError` | `(errors: Record<string, string>) => void` | Called when validation fails |
|
|
123
|
+
| `onStepChange` | `(step: number, total: number) => void` | Called when multi-step form changes step |
|
|
124
|
+
| `className` | `string` | Additional CSS class for the form container |
|
|
125
|
+
| `style` | `CSSProperties` | Inline styles |
|
|
126
|
+
| `designOverrides` | `Partial<FormDesign>` | Override form design settings |
|
|
127
|
+
|
|
128
|
+
## Field Types
|
|
129
|
+
|
|
130
|
+
| Type | Description |
|
|
131
|
+
| ---------------- | --------------------------- |
|
|
132
|
+
| `text` | Single-line text input |
|
|
133
|
+
| `textarea` | Multi-line text input |
|
|
134
|
+
| `email` | Email input with validation |
|
|
135
|
+
| `phone` | Phone number input |
|
|
136
|
+
| `number` | Numeric input with min/max |
|
|
137
|
+
| `url` | URL input with validation |
|
|
138
|
+
| `date` | Date picker |
|
|
139
|
+
| `time` | Time picker |
|
|
140
|
+
| `datetime` | Date and time picker |
|
|
141
|
+
| `select` | Dropdown select |
|
|
142
|
+
| `radio` | Radio button group |
|
|
143
|
+
| `checkbox` | Single checkbox |
|
|
144
|
+
| `checkbox_group` | Multiple checkboxes |
|
|
145
|
+
| `file` | File upload |
|
|
146
|
+
| `hidden` | Hidden field |
|
|
147
|
+
| `country` | Country selector |
|
|
148
|
+
| `state` | State/province selector |
|
|
149
|
+
| `html` | Static HTML content |
|
|
150
|
+
| `divider` | Visual divider |
|
|
151
|
+
| `recaptcha` | reCAPTCHA v2/v3 |
|
|
152
|
+
|
|
153
|
+
## Styling
|
|
154
|
+
|
|
155
|
+
Forms are styled using CSS variables. Override them via `designOverrides` or CSS:
|
|
156
|
+
|
|
157
|
+
```css
|
|
158
|
+
.lub-form {
|
|
159
|
+
--lub-primary-color: #3b82f6;
|
|
160
|
+
--lub-background-color: #ffffff;
|
|
161
|
+
--lub-text-color: #1f2937;
|
|
162
|
+
--lub-border-color: #e5e7eb;
|
|
163
|
+
--lub-font-family: system-ui, sans-serif;
|
|
164
|
+
--lub-font-size: 14px;
|
|
165
|
+
--lub-padding: 24px;
|
|
166
|
+
--lub-field-spacing: 16px;
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Or via JavaScript:
|
|
171
|
+
|
|
172
|
+
```jsx
|
|
173
|
+
<LubForm
|
|
174
|
+
formId="abc123"
|
|
175
|
+
designOverrides={{
|
|
176
|
+
primary_color: "#8b5cf6",
|
|
177
|
+
background_color: "#fafafa",
|
|
178
|
+
font_family: "Inter, sans-serif",
|
|
179
|
+
button_style: {
|
|
180
|
+
border_radius: "9999px",
|
|
181
|
+
full_width: true,
|
|
182
|
+
},
|
|
183
|
+
}}
|
|
184
|
+
/>
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## Conditional Logic
|
|
188
|
+
|
|
189
|
+
Fields support conditional visibility and requirements based on other field values:
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
// Show field when another field equals a value
|
|
193
|
+
conditional_logic: {
|
|
194
|
+
show_if: {
|
|
195
|
+
field_name: 'interest',
|
|
196
|
+
operator: 'equals',
|
|
197
|
+
value: 'other'
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Make field required based on condition
|
|
202
|
+
conditional_logic: {
|
|
203
|
+
required_if: {
|
|
204
|
+
field_name: 'newsletter',
|
|
205
|
+
operator: 'equals',
|
|
206
|
+
value: true
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Operators:** `equals`, `not_equals`, `contains`, `not_contains`, `greater_than`, `less_than`, `is_empty`, `is_not_empty`
|
|
212
|
+
|
|
213
|
+
## Custom Field Components
|
|
214
|
+
|
|
215
|
+
Register custom field types for advanced use cases:
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
import { registerFieldComponent } from "@lub/forms";
|
|
219
|
+
|
|
220
|
+
registerFieldComponent("rating", ({ field, register, error }) => (
|
|
221
|
+
<div className="rating-field">
|
|
222
|
+
{[1, 2, 3, 4, 5].map((value) => (
|
|
223
|
+
<label key={value}>
|
|
224
|
+
<input type="radio" value={value} {...register(field.name)} />
|
|
225
|
+
{"⭐".repeat(value)}
|
|
226
|
+
</label>
|
|
227
|
+
))}
|
|
228
|
+
{error && <span className="error">{error}</span>}
|
|
229
|
+
</div>
|
|
230
|
+
));
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## API Client
|
|
234
|
+
|
|
235
|
+
Use the client directly for custom integrations:
|
|
236
|
+
|
|
237
|
+
```tsx
|
|
238
|
+
import { LubFormsClient } from "@lub/forms";
|
|
239
|
+
|
|
240
|
+
const client = new LubFormsClient("https://api.lub.com");
|
|
241
|
+
|
|
242
|
+
// Fetch form definition
|
|
243
|
+
const form = await client.getForm("abc123");
|
|
244
|
+
|
|
245
|
+
// Submit form data
|
|
246
|
+
const response = await client.submitForm("abc123", {
|
|
247
|
+
data: { email: "user@example.com", name: "John" },
|
|
248
|
+
utm_parameters: { source: "landing-page" },
|
|
249
|
+
referrer: window.location.href,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Confirm double opt-in
|
|
253
|
+
const confirmation = await client.confirmOptIn("token123");
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## Global API (Script Tag)
|
|
257
|
+
|
|
258
|
+
When using the standalone or UMD build:
|
|
259
|
+
|
|
260
|
+
```javascript
|
|
261
|
+
// Render a form
|
|
262
|
+
LubForms.render(formId, containerIdOrElement, options);
|
|
263
|
+
|
|
264
|
+
// Auto-mount all forms with data-lub-form-id
|
|
265
|
+
LubForms.autoMount();
|
|
266
|
+
|
|
267
|
+
// Unmount a specific form
|
|
268
|
+
LubForms.unmount(containerId);
|
|
269
|
+
|
|
270
|
+
// Unmount all forms
|
|
271
|
+
LubForms.unmountAll();
|
|
272
|
+
|
|
273
|
+
// Initialize (called automatically)
|
|
274
|
+
LubForms.init();
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Development
|
|
278
|
+
|
|
279
|
+
```bash
|
|
280
|
+
bun install
|
|
281
|
+
bun run dev # Start dev server on :5174
|
|
282
|
+
bun run build # Build all formats
|
|
283
|
+
bun run lint # Run ESLint
|
|
284
|
+
bun run typecheck # TypeScript check
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
## Build Outputs
|
|
288
|
+
|
|
289
|
+
| File | Format | Use Case |
|
|
290
|
+
| ------------------------------ | ------ | ------------------------------ |
|
|
291
|
+
| `dist/lub-forms.es.js` | ESM | NPM package for React apps |
|
|
292
|
+
| `dist/lub-forms.umd.js` | UMD | Script tag with React on page |
|
|
293
|
+
| `dist/lub-forms.standalone.js` | IIFE | Drop-in script, bundles React |
|
|
294
|
+
| `dist/lub-forms.css` | CSS | Shared styles (UMD/ESM builds) |
|
|
295
|
+
|
|
296
|
+
## License
|
|
297
|
+
|
|
298
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.lub-form{--lub-bg-color: #ffffff;--lub-text-color: #1f2937;--lub-primary-color: #3b82f6;--lub-primary-hover: #2563eb;--lub-border-color: #d1d5db;--lub-error-color: #ef4444;--lub-success-color: #10b981;--lub-muted-color: #6b7280;--lub-font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;--lub-font-size: 16px;--lub-padding: 24px;--lub-field-spacing: 16px;--lub-border-radius: 6px;--lub-input-height: 42px;--lub-btn-bg: var(--lub-primary-color);--lub-btn-text: #ffffff;--lub-btn-radius: var(--lub-border-radius);--lub-focus-ring: 0 0 0 3px rgba(59, 130, 246, .3);--lub-transition: .15s ease}.lub-form{font-family:var(--lub-font-family);font-size:var(--lub-font-size);color:var(--lub-text-color);background-color:var(--lub-bg-color);padding:var(--lub-padding);border-radius:var(--lub-border-radius);box-sizing:border-box;line-height:1.5}.lub-form *,.lub-form *:before,.lub-form *:after{box-sizing:border-box}.lub-form__form{display:flex;flex-direction:column;gap:var(--lub-field-spacing)}.lub-form__header{margin-bottom:var(--lub-field-spacing)}.lub-form__title{font-size:1.5em;font-weight:600;margin:0 0 .5em;color:var(--lub-text-color)}.lub-form__description{margin:0;color:var(--lub-muted-color)}.lub-form__fields{display:flex;flex-direction:column;gap:var(--lub-field-spacing)}.lub-form__fields--two_column{display:flex;flex-wrap:wrap;gap:var(--lub-field-spacing)}.lub-form__field-group{display:contents}.lub-form__field-group--row{display:flex;flex-wrap:wrap;gap:var(--lub-field-spacing);width:100%}.lub-form__field,.lub-form__field--full{width:100%}.lub-form__field--half{width:calc(50% - var(--lub-field-spacing) / 2)}.lub-form__field--third{width:calc(33.333% - var(--lub-field-spacing) * 2 / 3)}.lub-form__field--quarter{width:calc(25% - var(--lub-field-spacing) * 3 / 4)}@media(max-width:640px){.lub-form__field--half,.lub-form__field--third,.lub-form__field--quarter{width:100%}}.lub-form__field-wrapper{display:flex;flex-direction:column;gap:6px}.lub-form__label{display:flex;align-items:center;gap:4px;font-size:.875em;font-weight:500;color:var(--lub-text-color)}.lub-form__required-indicator{color:var(--lub-error-color);font-weight:400}.lub-form__input,.lub-form__textarea,.lub-form__select{width:100%;padding:10px 12px;font-family:inherit;font-size:1em;color:var(--lub-text-color);background-color:var(--lub-bg-color);border:1px solid var(--lub-border-color);border-radius:var(--lub-border-radius);transition:border-color var(--lub-transition),box-shadow var(--lub-transition);outline:none}.lub-form__input{height:var(--lub-input-height)}.lub-form__textarea{min-height:100px;resize:vertical}.lub-form__select{height:var(--lub-input-height);cursor:pointer;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 20 20' fill='%236b7280'%3E%3Cpath fill-rule='evenodd' d='M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z' clip-rule='evenodd'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 10px center;padding-right:36px}.lub-form__input::placeholder,.lub-form__textarea::placeholder{color:var(--lub-muted-color)}.lub-form__input:focus,.lub-form__textarea:focus,.lub-form__select:focus{border-color:var(--lub-primary-color);box-shadow:var(--lub-focus-ring)}.lub-form__input--error,.lub-form__textarea--error,.lub-form__select--error{border-color:var(--lub-error-color)}.lub-form__input--error:focus,.lub-form__textarea--error:focus,.lub-form__select--error:focus{border-color:var(--lub-error-color);box-shadow:0 0 0 3px #ef44444d}.lub-form__checkbox-group,.lub-form__radio-group{display:flex;flex-direction:column;gap:8px}.lub-form__checkbox-label,.lub-form__radio-label{display:flex;align-items:flex-start;gap:10px;cursor:pointer;font-size:.9375em}.lub-form__checkbox,.lub-form__radio{width:18px;height:18px;margin:2px 0 0;flex-shrink:0;cursor:pointer;accent-color:var(--lub-primary-color)}.lub-form__checkbox-text,.lub-form__radio-text{color:var(--lub-text-color)}.lub-form__file-input{width:100%;padding:10px 12px;font-family:inherit;font-size:.9375em;color:var(--lub-text-color);background-color:var(--lub-bg-color);border:1px dashed var(--lub-border-color);border-radius:var(--lub-border-radius);cursor:pointer;transition:border-color var(--lub-transition),background-color var(--lub-transition)}.lub-form__file-input:hover{background-color:#00000005}.lub-form__file-input:focus{border-color:var(--lub-primary-color);outline:none}.lub-form__file-hint{margin:4px 0 0;font-size:.8125em;color:var(--lub-muted-color)}.lub-form__help-text{margin:0;font-size:.8125em;color:var(--lub-muted-color)}.lub-form__error{margin:0;font-size:.8125em;color:var(--lub-error-color)}.lub-form__html-content{padding:8px 0;color:var(--lub-text-color)}.lub-form__html-content a{color:var(--lub-primary-color);text-decoration:underline}.lub-form__divider{display:flex;align-items:center;gap:12px;padding:8px 0}.lub-form__divider-line{flex:1;height:1px;background-color:var(--lub-border-color);border:none;margin:0}.lub-form__divider-label{font-size:.875em;color:var(--lub-muted-color);white-space:nowrap}.lub-form__consent{padding-top:var(--lub-field-spacing)}.lub-form__consent-label{display:flex;align-items:flex-start;gap:10px;cursor:pointer}.lub-form__consent-checkbox{width:18px;height:18px;margin-top:2px;flex-shrink:0;cursor:pointer;accent-color:var(--lub-primary-color)}.lub-form__consent-text{font-size:.875em;color:var(--lub-text-color)}.lub-form__consent-text a{color:var(--lub-primary-color);text-decoration:underline}.lub-form__actions{display:flex;justify-content:flex-end;padding-top:var(--lub-field-spacing)}.lub-form__button{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:12px 24px;font-family:inherit;font-size:1em;font-weight:500;border:none;border-radius:var(--lub-btn-radius);cursor:pointer;transition:background-color var(--lub-transition),opacity var(--lub-transition)}.lub-form__button--primary{color:var(--lub-btn-text);background-color:var(--lub-btn-bg)}.lub-form__button--primary:hover:not(:disabled){background-color:var(--lub-primary-hover)}.lub-form__button--secondary{color:var(--lub-text-color);background-color:transparent;border:1px solid var(--lub-border-color)}.lub-form__button--secondary:hover:not(:disabled){background-color:#0000000d}.lub-form__button:disabled{opacity:.6;cursor:not-allowed}.lub-form__button-loading{display:inline-flex;align-items:center;gap:8px}.lub-form__navigation{display:flex;align-items:center;gap:12px;padding-top:var(--lub-field-spacing)}.lub-form__navigation-spacer{flex:1}.lub-form__steps{padding-bottom:var(--lub-field-spacing)}.lub-form__steps-list{display:flex;align-items:center;list-style:none;margin:0;padding:0;gap:0}.lub-form__step{display:flex;align-items:center;flex:1}.lub-form__step:last-child{flex:0 0 auto}.lub-form__step-indicator,.lub-form__step-button{display:flex;align-items:center;gap:8px;padding:8px;background:none;border:none;font-family:inherit;font-size:.875em;color:var(--lub-muted-color)}.lub-form__step-button{cursor:pointer}.lub-form__step-button:hover{color:var(--lub-primary-color)}.lub-form__step-number{display:flex;align-items:center;justify-content:center;width:28px;height:28px;border-radius:50%;background-color:var(--lub-border-color);color:var(--lub-muted-color);font-weight:500;font-size:.8125em}.lub-form__step--active .lub-form__step-number{background-color:var(--lub-primary-color);color:#fff}.lub-form__step--completed .lub-form__step-number{background-color:var(--lub-success-color);color:#fff}.lub-form__step-name{display:none}@media(min-width:640px){.lub-form__step-name{display:inline}}.lub-form__step--active .lub-form__step-indicator,.lub-form__step--active .lub-form__step-button{color:var(--lub-text-color)}.lub-form__step-connector{flex:1;height:2px;background-color:var(--lub-border-color);margin:0 8px}.lub-form__step-connector--completed{background-color:var(--lub-success-color)}.lub-form__check-icon{width:16px;height:16px}.lub-form__step-description{margin:12px 0 0;font-size:.875em;color:var(--lub-muted-color)}.lub-form__spinner{width:18px;height:18px;animation:lub-spin 1s linear infinite}.lub-form__spinner-track{opacity:.25}.lub-form__spinner-head{opacity:1}@keyframes lub-spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.lub-form--loading .lub-form__skeleton{display:flex;flex-direction:column;gap:var(--lub-field-spacing)}.lub-form__skeleton-title,.lub-form__skeleton-field,.lub-form__skeleton-button{background:linear-gradient(90deg,var(--lub-border-color) 25%,rgba(0,0,0,.05) 50%,var(--lub-border-color) 75%);background-size:200% 100%;animation:lub-shimmer 1.5s infinite;border-radius:var(--lub-border-radius)}.lub-form__skeleton-title{height:28px;width:60%;margin-bottom:8px}.lub-form__skeleton-field{height:var(--lub-input-height)}.lub-form__skeleton-field--short{width:50%}.lub-form__skeleton-button{height:46px;width:120px;margin-left:auto}@keyframes lub-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}.lub-form--success .lub-form__success{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:var(--lub-padding);min-height:200px}.lub-form__success-icon{width:64px;height:64px;color:var(--lub-success-color);margin-bottom:16px}.lub-form__success-message{font-size:1.125em;color:var(--lub-text-color);margin:0}.lub-form--error .lub-form__error-container{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:var(--lub-padding);min-height:150px}.lub-form__error-message{font-size:1em;color:var(--lub-error-color);margin:0}.lub-form__recaptcha{display:flex;justify-content:flex-start;padding:8px 0}.lub-form__recaptcha-widget{transform-origin:left top}.lub-form[data-theme=dark]{--lub-bg-color: #1f2937;--lub-text-color: #f9fafb;--lub-border-color: #4b5563;--lub-muted-color: #9ca3af}
|