@duffcloudservices/site-forms 0.1.0 → 0.1.2
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 +260 -213
- package/dist/index.js +13 -13
- package/dist/index.js.map +1 -1
- package/dist/site-forms.css +1 -0
- package/package.json +72 -73
- package/src/DcsForm.vue +303 -299
- package/src/__tests__/fields.test.ts +82 -82
- package/src/__tests__/multi-step.test.ts +46 -46
- package/src/__tests__/schema.test.ts +42 -42
- package/src/__tests__/style-import.test.ts +9 -0
- package/src/__tests__/submission.test.ts +77 -77
- package/src/__tests__/visible-if.test.ts +111 -111
- package/src/composables/useDcsForm.ts +201 -201
- package/src/composables/useFormSubmission.ts +113 -113
- package/src/composables/useFormValidation.ts +127 -127
- package/src/fields/DcsFormCheckbox.vue +35 -35
- package/src/fields/DcsFormCheckboxGroup.vue +52 -52
- package/src/fields/DcsFormDate.vue +34 -34
- package/src/fields/DcsFormFieldWrapper.vue +39 -39
- package/src/fields/DcsFormFile.vue +38 -38
- package/src/fields/DcsFormHidden.vue +17 -17
- package/src/fields/DcsFormHtmlBlock.vue +19 -19
- package/src/fields/DcsFormRadio.vue +45 -45
- package/src/fields/DcsFormSection.vue +19 -19
- package/src/fields/DcsFormSelect.vue +62 -62
- package/src/fields/DcsFormText.vue +54 -54
- package/src/fields/DcsFormTextarea.vue +43 -43
- package/src/index.ts +53 -51
- package/src/loaders/yaml.ts +51 -51
- package/src/schema/validate.ts +58 -58
- package/src/shims.d.ts +10 -10
- package/src/style.css +221 -0
- package/src/types.ts +140 -140
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
<script setup lang="ts">
|
|
2
|
-
import { computed } from 'vue'
|
|
3
|
-
import type { PortalFormField } from '../types'
|
|
4
|
-
import DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'
|
|
5
|
-
|
|
6
|
-
const props = defineProps<{
|
|
7
|
-
field: PortalFormField
|
|
8
|
-
modelValue: string | undefined
|
|
9
|
-
error?: string
|
|
10
|
-
}>()
|
|
11
|
-
|
|
12
|
-
const emit = defineEmits<{
|
|
13
|
-
'update:modelValue': [value: string]
|
|
14
|
-
blur: []
|
|
15
|
-
}>()
|
|
16
|
-
|
|
17
|
-
const inputId = computed(() => `field-${props.field.id}`)
|
|
18
|
-
</script>
|
|
19
|
-
|
|
20
|
-
<template>
|
|
21
|
-
<DcsFormFieldWrapper :field="field" :error="error" :input-id="inputId">
|
|
22
|
-
<slot
|
|
23
|
-
name="input"
|
|
24
|
-
:id="inputId"
|
|
25
|
-
:value="modelValue"
|
|
26
|
-
:on-input="(e: Event) => emit('update:modelValue', (e.target as HTMLTextAreaElement).value)"
|
|
27
|
-
:on-blur="() => emit('blur')"
|
|
28
|
-
>
|
|
29
|
-
<textarea
|
|
30
|
-
:id="inputId"
|
|
31
|
-
class="dcs-form-input dcs-form-textarea"
|
|
32
|
-
:name="field.id"
|
|
33
|
-
:placeholder="field.placeholder"
|
|
34
|
-
:required="field.required"
|
|
35
|
-
:aria-invalid="!!error"
|
|
36
|
-
rows="5"
|
|
37
|
-
:value="modelValue ?? ''"
|
|
38
|
-
@input="emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)"
|
|
39
|
-
@blur="emit('blur')"
|
|
40
|
-
/>
|
|
41
|
-
</slot>
|
|
42
|
-
</DcsFormFieldWrapper>
|
|
43
|
-
</template>
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
import type { PortalFormField } from '../types'
|
|
4
|
+
import DcsFormFieldWrapper from './DcsFormFieldWrapper.vue'
|
|
5
|
+
|
|
6
|
+
const props = defineProps<{
|
|
7
|
+
field: PortalFormField
|
|
8
|
+
modelValue: string | undefined
|
|
9
|
+
error?: string
|
|
10
|
+
}>()
|
|
11
|
+
|
|
12
|
+
const emit = defineEmits<{
|
|
13
|
+
'update:modelValue': [value: string]
|
|
14
|
+
blur: []
|
|
15
|
+
}>()
|
|
16
|
+
|
|
17
|
+
const inputId = computed(() => `field-${props.field.id}`)
|
|
18
|
+
</script>
|
|
19
|
+
|
|
20
|
+
<template>
|
|
21
|
+
<DcsFormFieldWrapper :field="field" :error="error" :input-id="inputId">
|
|
22
|
+
<slot
|
|
23
|
+
name="input"
|
|
24
|
+
:id="inputId"
|
|
25
|
+
:value="modelValue"
|
|
26
|
+
:on-input="(e: Event) => emit('update:modelValue', (e.target as HTMLTextAreaElement).value)"
|
|
27
|
+
:on-blur="() => emit('blur')"
|
|
28
|
+
>
|
|
29
|
+
<textarea
|
|
30
|
+
:id="inputId"
|
|
31
|
+
class="dcs-form-input dcs-form-textarea"
|
|
32
|
+
:name="field.id"
|
|
33
|
+
:placeholder="field.placeholder"
|
|
34
|
+
:required="field.required"
|
|
35
|
+
:aria-invalid="!!error"
|
|
36
|
+
rows="5"
|
|
37
|
+
:value="modelValue ?? ''"
|
|
38
|
+
@input="emit('update:modelValue', ($event.target as HTMLTextAreaElement).value)"
|
|
39
|
+
@blur="emit('blur')"
|
|
40
|
+
/>
|
|
41
|
+
</slot>
|
|
42
|
+
</DcsFormFieldWrapper>
|
|
43
|
+
</template>
|
package/src/index.ts
CHANGED
|
@@ -1,51 +1,53 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export { default as
|
|
4
|
-
export { default as
|
|
5
|
-
export { default as
|
|
6
|
-
export { default as
|
|
7
|
-
export { default as
|
|
8
|
-
export { default as
|
|
9
|
-
export { default as
|
|
10
|
-
export { default as
|
|
11
|
-
export { default as
|
|
12
|
-
export { default as
|
|
13
|
-
export { default as
|
|
14
|
-
|
|
15
|
-
export {
|
|
16
|
-
|
|
17
|
-
export {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
export {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
export type {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
1
|
+
import './style.css'
|
|
2
|
+
|
|
3
|
+
export { default as DcsForm } from './DcsForm.vue'
|
|
4
|
+
export { default as DcsFormText } from './fields/DcsFormText.vue'
|
|
5
|
+
export { default as DcsFormTextarea } from './fields/DcsFormTextarea.vue'
|
|
6
|
+
export { default as DcsFormSelect } from './fields/DcsFormSelect.vue'
|
|
7
|
+
export { default as DcsFormRadio } from './fields/DcsFormRadio.vue'
|
|
8
|
+
export { default as DcsFormCheckboxGroup } from './fields/DcsFormCheckboxGroup.vue'
|
|
9
|
+
export { default as DcsFormCheckbox } from './fields/DcsFormCheckbox.vue'
|
|
10
|
+
export { default as DcsFormDate } from './fields/DcsFormDate.vue'
|
|
11
|
+
export { default as DcsFormFile } from './fields/DcsFormFile.vue'
|
|
12
|
+
export { default as DcsFormHidden } from './fields/DcsFormHidden.vue'
|
|
13
|
+
export { default as DcsFormSection } from './fields/DcsFormSection.vue'
|
|
14
|
+
export { default as DcsFormHtmlBlock } from './fields/DcsFormHtmlBlock.vue'
|
|
15
|
+
export { default as DcsFormFieldWrapper } from './fields/DcsFormFieldWrapper.vue'
|
|
16
|
+
|
|
17
|
+
export { useDcsForm } from './composables/useDcsForm'
|
|
18
|
+
export type { UseDcsFormOptions, UseDcsFormReturn } from './composables/useDcsForm'
|
|
19
|
+
export {
|
|
20
|
+
validateField,
|
|
21
|
+
validateForm,
|
|
22
|
+
isFieldVisible,
|
|
23
|
+
hasErrors,
|
|
24
|
+
} from './composables/useFormValidation'
|
|
25
|
+
export { submitFormValues } from './composables/useFormSubmission'
|
|
26
|
+
export type { SubmitOptions } from './composables/useFormSubmission'
|
|
27
|
+
|
|
28
|
+
export { loadFormDefinitions, parseFormYaml } from './loaders/yaml'
|
|
29
|
+
export {
|
|
30
|
+
validateFormDefinition,
|
|
31
|
+
warnIfInvalid,
|
|
32
|
+
} from './schema/validate'
|
|
33
|
+
export type { SchemaValidationResult } from './schema/validate'
|
|
34
|
+
|
|
35
|
+
export type {
|
|
36
|
+
DcsFormSubmitPayload,
|
|
37
|
+
DcsFormSubmitSuccess,
|
|
38
|
+
DcsFormSubmitError,
|
|
39
|
+
FormErrors,
|
|
40
|
+
FormValues,
|
|
41
|
+
PortalFormDefinition,
|
|
42
|
+
PortalFormField,
|
|
43
|
+
PortalFormFieldType,
|
|
44
|
+
PortalFormFieldOption,
|
|
45
|
+
PortalFormFieldValidation,
|
|
46
|
+
PortalFormFieldVisibleIf,
|
|
47
|
+
PortalFormFieldWidth,
|
|
48
|
+
PortalFormStep,
|
|
49
|
+
PortalFormSubmissionConfig,
|
|
50
|
+
PortalFormSubmissionLeadConfig,
|
|
51
|
+
PortalFormSubmissionEmailConfig,
|
|
52
|
+
PortalFormSubmissionWebhookConfig,
|
|
53
|
+
} from './types'
|
package/src/loaders/yaml.ts
CHANGED
|
@@ -1,51 +1,51 @@
|
|
|
1
|
-
import yaml from 'js-yaml'
|
|
2
|
-
import type { PortalFormDefinition } from '../types'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Eagerly load every form YAML under `/.dcs/forms/*.yaml` from the
|
|
6
|
-
* consuming Vite app and parse them into PortalFormDefinition objects.
|
|
7
|
-
*
|
|
8
|
-
* Vite returns the modules as raw strings (when using `?raw` or the
|
|
9
|
-
* default text loader for unknown extensions) or as parsed objects
|
|
10
|
-
* (when `vite-plugin-yaml` is installed). This helper handles both.
|
|
11
|
-
*/
|
|
12
|
-
export function loadFormDefinitions(
|
|
13
|
-
modules: Record<string, unknown>,
|
|
14
|
-
): Record<string, PortalFormDefinition> {
|
|
15
|
-
const out: Record<string, PortalFormDefinition> = {}
|
|
16
|
-
for (const [path, mod] of Object.entries(modules)) {
|
|
17
|
-
const formId = extractFormId(path)
|
|
18
|
-
const def = parseModule(mod)
|
|
19
|
-
if (def) {
|
|
20
|
-
out[def.formId ?? formId] = def
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
return out
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function extractFormId(path: string): string {
|
|
27
|
-
const file = path.split('/').pop() ?? path
|
|
28
|
-
return file.replace(/\.ya?ml$/i, '')
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function parseModule(mod: unknown): PortalFormDefinition | null {
|
|
32
|
-
if (!mod) return null
|
|
33
|
-
if (typeof mod === 'string') {
|
|
34
|
-
return yaml.load(mod) as PortalFormDefinition
|
|
35
|
-
}
|
|
36
|
-
if (typeof mod === 'object') {
|
|
37
|
-
// vite-plugin-yaml returns parsed objects, possibly wrapped in
|
|
38
|
-
// `{ default: ... }` when imported via `import: 'default'`.
|
|
39
|
-
const maybeDefault = (mod as { default?: unknown }).default
|
|
40
|
-
if (maybeDefault && typeof maybeDefault === 'object') {
|
|
41
|
-
return maybeDefault as PortalFormDefinition
|
|
42
|
-
}
|
|
43
|
-
return mod as PortalFormDefinition
|
|
44
|
-
}
|
|
45
|
-
return null
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Parse a single raw YAML string into a PortalFormDefinition. */
|
|
49
|
-
export function parseFormYaml(raw: string): PortalFormDefinition {
|
|
50
|
-
return yaml.load(raw) as PortalFormDefinition
|
|
51
|
-
}
|
|
1
|
+
import yaml from 'js-yaml'
|
|
2
|
+
import type { PortalFormDefinition } from '../types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Eagerly load every form YAML under `/.dcs/forms/*.yaml` from the
|
|
6
|
+
* consuming Vite app and parse them into PortalFormDefinition objects.
|
|
7
|
+
*
|
|
8
|
+
* Vite returns the modules as raw strings (when using `?raw` or the
|
|
9
|
+
* default text loader for unknown extensions) or as parsed objects
|
|
10
|
+
* (when `vite-plugin-yaml` is installed). This helper handles both.
|
|
11
|
+
*/
|
|
12
|
+
export function loadFormDefinitions(
|
|
13
|
+
modules: Record<string, unknown>,
|
|
14
|
+
): Record<string, PortalFormDefinition> {
|
|
15
|
+
const out: Record<string, PortalFormDefinition> = {}
|
|
16
|
+
for (const [path, mod] of Object.entries(modules)) {
|
|
17
|
+
const formId = extractFormId(path)
|
|
18
|
+
const def = parseModule(mod)
|
|
19
|
+
if (def) {
|
|
20
|
+
out[def.formId ?? formId] = def
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return out
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function extractFormId(path: string): string {
|
|
27
|
+
const file = path.split('/').pop() ?? path
|
|
28
|
+
return file.replace(/\.ya?ml$/i, '')
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseModule(mod: unknown): PortalFormDefinition | null {
|
|
32
|
+
if (!mod) return null
|
|
33
|
+
if (typeof mod === 'string') {
|
|
34
|
+
return yaml.load(mod) as PortalFormDefinition
|
|
35
|
+
}
|
|
36
|
+
if (typeof mod === 'object') {
|
|
37
|
+
// vite-plugin-yaml returns parsed objects, possibly wrapped in
|
|
38
|
+
// `{ default: ... }` when imported via `import: 'default'`.
|
|
39
|
+
const maybeDefault = (mod as { default?: unknown }).default
|
|
40
|
+
if (maybeDefault && typeof maybeDefault === 'object') {
|
|
41
|
+
return maybeDefault as PortalFormDefinition
|
|
42
|
+
}
|
|
43
|
+
return mod as PortalFormDefinition
|
|
44
|
+
}
|
|
45
|
+
return null
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Parse a single raw YAML string into a PortalFormDefinition. */
|
|
49
|
+
export function parseFormYaml(raw: string): PortalFormDefinition {
|
|
50
|
+
return yaml.load(raw) as PortalFormDefinition
|
|
51
|
+
}
|
package/src/schema/validate.ts
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validates a loaded form definition against the form-definition JSON
|
|
3
|
-
* Schema emitted by `@dcs/contracts`. Dev-only — failures are logged
|
|
4
|
-
* via `console.warn` rather than thrown so a malformed YAML never
|
|
5
|
-
* takes down a customer site in production.
|
|
6
|
-
*
|
|
7
|
-
* Schema is bundled as a snapshot of
|
|
8
|
-
* `contracts/dist/form-definition.schema.json` to avoid a runtime
|
|
9
|
-
* dependency on the contracts build output.
|
|
10
|
-
*/
|
|
11
|
-
import Ajv, { type ErrorObject, type ValidateFunction } from 'ajv'
|
|
12
|
-
import addFormats from 'ajv-formats'
|
|
13
|
-
import schema from './form-definition.schema.json'
|
|
14
|
-
import type { PortalFormDefinition } from '../types'
|
|
15
|
-
|
|
16
|
-
let validator: ValidateFunction | null = null
|
|
17
|
-
|
|
18
|
-
function getValidator(): ValidateFunction {
|
|
19
|
-
if (validator) return validator
|
|
20
|
-
const ajv = new Ajv({
|
|
21
|
-
allErrors: true,
|
|
22
|
-
strict: false,
|
|
23
|
-
discriminator: false,
|
|
24
|
-
})
|
|
25
|
-
addFormats(ajv)
|
|
26
|
-
validator = ajv.compile(schema as object)
|
|
27
|
-
return validator
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface SchemaValidationResult {
|
|
31
|
-
valid: boolean
|
|
32
|
-
errors: ErrorObject[]
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
export function validateFormDefinition(
|
|
36
|
-
def: PortalFormDefinition | unknown,
|
|
37
|
-
): SchemaValidationResult {
|
|
38
|
-
const v = getValidator()
|
|
39
|
-
const valid = v(def) as boolean
|
|
40
|
-
return { valid, errors: valid ? [] : (v.errors ?? []) }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Logs a `console.warn` when invalid, but only when `import.meta.env.DEV`. */
|
|
44
|
-
export function warnIfInvalid(
|
|
45
|
-
formId: string,
|
|
46
|
-
def: PortalFormDefinition | unknown,
|
|
47
|
-
isDev: boolean,
|
|
48
|
-
): SchemaValidationResult {
|
|
49
|
-
const result = validateFormDefinition(def)
|
|
50
|
-
if (!result.valid && isDev) {
|
|
51
|
-
// eslint-disable-next-line no-console
|
|
52
|
-
console.warn(
|
|
53
|
-
`[@duffcloudservices/site-forms] Form "${formId}" failed schema validation:`,
|
|
54
|
-
result.errors,
|
|
55
|
-
)
|
|
56
|
-
}
|
|
57
|
-
return result
|
|
58
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Validates a loaded form definition against the form-definition JSON
|
|
3
|
+
* Schema emitted by `@dcs/contracts`. Dev-only — failures are logged
|
|
4
|
+
* via `console.warn` rather than thrown so a malformed YAML never
|
|
5
|
+
* takes down a customer site in production.
|
|
6
|
+
*
|
|
7
|
+
* Schema is bundled as a snapshot of
|
|
8
|
+
* `contracts/dist/form-definition.schema.json` to avoid a runtime
|
|
9
|
+
* dependency on the contracts build output.
|
|
10
|
+
*/
|
|
11
|
+
import Ajv, { type ErrorObject, type ValidateFunction } from 'ajv'
|
|
12
|
+
import addFormats from 'ajv-formats'
|
|
13
|
+
import schema from './form-definition.schema.json'
|
|
14
|
+
import type { PortalFormDefinition } from '../types'
|
|
15
|
+
|
|
16
|
+
let validator: ValidateFunction | null = null
|
|
17
|
+
|
|
18
|
+
function getValidator(): ValidateFunction {
|
|
19
|
+
if (validator) return validator
|
|
20
|
+
const ajv = new Ajv({
|
|
21
|
+
allErrors: true,
|
|
22
|
+
strict: false,
|
|
23
|
+
discriminator: false,
|
|
24
|
+
})
|
|
25
|
+
addFormats(ajv)
|
|
26
|
+
validator = ajv.compile(schema as object)
|
|
27
|
+
return validator
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SchemaValidationResult {
|
|
31
|
+
valid: boolean
|
|
32
|
+
errors: ErrorObject[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function validateFormDefinition(
|
|
36
|
+
def: PortalFormDefinition | unknown,
|
|
37
|
+
): SchemaValidationResult {
|
|
38
|
+
const v = getValidator()
|
|
39
|
+
const valid = v(def) as boolean
|
|
40
|
+
return { valid, errors: valid ? [] : (v.errors ?? []) }
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Logs a `console.warn` when invalid, but only when `import.meta.env.DEV`. */
|
|
44
|
+
export function warnIfInvalid(
|
|
45
|
+
formId: string,
|
|
46
|
+
def: PortalFormDefinition | unknown,
|
|
47
|
+
isDev: boolean,
|
|
48
|
+
): SchemaValidationResult {
|
|
49
|
+
const result = validateFormDefinition(def)
|
|
50
|
+
if (!result.valid && isDev) {
|
|
51
|
+
// eslint-disable-next-line no-console
|
|
52
|
+
console.warn(
|
|
53
|
+
`[@duffcloudservices/site-forms] Form "${formId}" failed schema validation:`,
|
|
54
|
+
result.errors,
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
return result
|
|
58
|
+
}
|
package/src/shims.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
declare module '*.vue' {
|
|
2
|
-
import type { DefineComponent } from 'vue'
|
|
3
|
-
const component: DefineComponent<{}, {}, unknown>
|
|
4
|
-
export default component
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
declare module '*.json' {
|
|
8
|
-
const value: unknown
|
|
9
|
-
export default value
|
|
10
|
-
}
|
|
1
|
+
declare module '*.vue' {
|
|
2
|
+
import type { DefineComponent } from 'vue'
|
|
3
|
+
const component: DefineComponent<{}, {}, unknown>
|
|
4
|
+
export default component
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
declare module '*.json' {
|
|
8
|
+
const value: unknown
|
|
9
|
+
export default value
|
|
10
|
+
}
|
package/src/style.css
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
.dcs-form {
|
|
2
|
+
display: grid;
|
|
3
|
+
gap: 1.5rem;
|
|
4
|
+
width: 100%;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
.dcs-form__header {
|
|
8
|
+
display: grid;
|
|
9
|
+
gap: 0.5rem;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.dcs-form__title {
|
|
13
|
+
margin: 0;
|
|
14
|
+
font-size: clamp(1.875rem, 2vw + 1.25rem, 2.5rem);
|
|
15
|
+
font-weight: 700;
|
|
16
|
+
line-height: 1.1;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.dcs-form__description {
|
|
20
|
+
margin: 0;
|
|
21
|
+
font-size: 1rem;
|
|
22
|
+
line-height: 1.6;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.dcs-form__progress {
|
|
26
|
+
font-size: 0.95rem;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.dcs-form__fields {
|
|
30
|
+
display: grid;
|
|
31
|
+
gap: 1rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.dcs-form-field {
|
|
35
|
+
display: grid;
|
|
36
|
+
gap: 0.5rem;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.dcs-form-field__label {
|
|
40
|
+
display: inline-flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: 0.2rem;
|
|
43
|
+
font-size: 0.95rem;
|
|
44
|
+
font-weight: 600;
|
|
45
|
+
line-height: 1.4;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.dcs-form-field__required {
|
|
49
|
+
font-weight: 700;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.dcs-form-input {
|
|
53
|
+
width: 100%;
|
|
54
|
+
min-height: 3rem;
|
|
55
|
+
padding: 0.75rem 0.95rem;
|
|
56
|
+
font: inherit;
|
|
57
|
+
line-height: 1.5;
|
|
58
|
+
color: inherit;
|
|
59
|
+
background: rgba(255, 255, 255, 0.96);
|
|
60
|
+
border: 1px solid rgba(15, 23, 42, 0.16);
|
|
61
|
+
border-radius: 0.875rem;
|
|
62
|
+
box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);
|
|
63
|
+
transition:
|
|
64
|
+
border-color 0.15s ease,
|
|
65
|
+
box-shadow 0.15s ease,
|
|
66
|
+
background-color 0.15s ease;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.dcs-form-input::placeholder {
|
|
70
|
+
color: rgba(71, 85, 105, 0.75);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.dcs-form-input:hover {
|
|
74
|
+
border-color: rgba(37, 99, 235, 0.38);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.dcs-form-input:focus,
|
|
78
|
+
.dcs-form-input:focus-visible {
|
|
79
|
+
outline: none;
|
|
80
|
+
border-color: rgba(37, 99, 235, 0.75);
|
|
81
|
+
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.18);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.dcs-form-input[aria-invalid='true'] {
|
|
85
|
+
border-color: rgba(220, 38, 38, 0.72);
|
|
86
|
+
box-shadow: 0 0 0 4px rgba(248, 113, 113, 0.18);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.dcs-form-textarea {
|
|
90
|
+
min-height: 10rem;
|
|
91
|
+
resize: vertical;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.dcs-form-select {
|
|
95
|
+
appearance: none;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.dcs-form-field__help,
|
|
99
|
+
.dcs-form-field__error,
|
|
100
|
+
.dcs-form__submit-error {
|
|
101
|
+
margin: 0;
|
|
102
|
+
font-size: 0.875rem;
|
|
103
|
+
line-height: 1.5;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.dcs-form-field__error,
|
|
107
|
+
.dcs-form__submit-error {
|
|
108
|
+
color: #b91c1c;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.dcs-form__submit-error {
|
|
112
|
+
padding: 0.85rem 1rem;
|
|
113
|
+
border: 1px solid rgba(220, 38, 38, 0.28);
|
|
114
|
+
border-radius: 0.875rem;
|
|
115
|
+
background: rgba(254, 242, 242, 0.92);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.dcs-form__actions {
|
|
119
|
+
display: flex;
|
|
120
|
+
flex-wrap: wrap;
|
|
121
|
+
gap: 0.75rem;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.dcs-form__btn {
|
|
125
|
+
appearance: none;
|
|
126
|
+
border: 0;
|
|
127
|
+
border-radius: 9999px;
|
|
128
|
+
padding: 0.85rem 1.5rem;
|
|
129
|
+
font: inherit;
|
|
130
|
+
font-weight: 700;
|
|
131
|
+
line-height: 1;
|
|
132
|
+
cursor: pointer;
|
|
133
|
+
transition:
|
|
134
|
+
transform 0.15s ease,
|
|
135
|
+
box-shadow 0.15s ease,
|
|
136
|
+
opacity 0.15s ease;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
.dcs-form__btn:hover {
|
|
140
|
+
transform: translateY(-1px);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.dcs-form__btn:focus,
|
|
144
|
+
.dcs-form__btn:focus-visible {
|
|
145
|
+
outline: none;
|
|
146
|
+
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.22);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
.dcs-form__btn:disabled {
|
|
150
|
+
opacity: 0.7;
|
|
151
|
+
cursor: not-allowed;
|
|
152
|
+
transform: none;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
.dcs-form__btn--submit,
|
|
156
|
+
.dcs-form__btn--next {
|
|
157
|
+
color: #fff;
|
|
158
|
+
background: linear-gradient(135deg, #1d4ed8, #2563eb);
|
|
159
|
+
box-shadow: 0 10px 24px rgba(37, 99, 235, 0.22);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.dcs-form__btn--prev {
|
|
163
|
+
color: #0f172a;
|
|
164
|
+
background: rgba(255, 255, 255, 0.92);
|
|
165
|
+
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.12);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.dcs-form-checkbox-group,
|
|
169
|
+
.dcs-form-radio-group {
|
|
170
|
+
display: grid;
|
|
171
|
+
gap: 0.75rem;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.dcs-form-checkbox,
|
|
175
|
+
.dcs-form-radio,
|
|
176
|
+
.dcs-form-checkbox-single {
|
|
177
|
+
display: flex;
|
|
178
|
+
align-items: flex-start;
|
|
179
|
+
gap: 0.65rem;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.dcs-form-checkbox-single input,
|
|
183
|
+
.dcs-form-checkbox input,
|
|
184
|
+
.dcs-form-radio input {
|
|
185
|
+
margin-top: 0.2rem;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.dcs-form-section {
|
|
189
|
+
display: grid;
|
|
190
|
+
gap: 0.4rem;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
.dcs-form-section__heading,
|
|
194
|
+
.dcs-form-section__help,
|
|
195
|
+
.dcs-form--missing p,
|
|
196
|
+
.dcs-form--success p {
|
|
197
|
+
margin: 0;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
@media (min-width: 768px) {
|
|
201
|
+
.dcs-form__fields {
|
|
202
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
203
|
+
gap: 1.25rem;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
.dcs-form-field--width-half {
|
|
207
|
+
grid-column: span 1;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.dcs-form-field--width-full,
|
|
211
|
+
.dcs-form-field--width-auto,
|
|
212
|
+
.dcs-form-field--textarea,
|
|
213
|
+
.dcs-form-field--checkbox,
|
|
214
|
+
.dcs-form-field--checkbox-group,
|
|
215
|
+
.dcs-form-field--radio,
|
|
216
|
+
.dcs-form-field--section-heading,
|
|
217
|
+
.dcs-form-field--html-block,
|
|
218
|
+
.dcs-form-field--file {
|
|
219
|
+
grid-column: 1 / -1;
|
|
220
|
+
}
|
|
221
|
+
}
|