Skip to content

Commit 596ad8e

Browse files
committed
feat: signup form uses form module components
1 parent 23dcacb commit 596ad8e

File tree

9 files changed

+1284
-11
lines changed

9 files changed

+1284
-11
lines changed

components/form/field-container.vue

+207
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
<template>
2+
<ZeroFormField
3+
v-slot="{ field, fieldId, fieldType, state, required, disabled, validationMessage, updateValue, toggleState }"
4+
v-bind="props"
5+
:class="['field-container', { focused }]">
6+
7+
<label v-if="scaffold.label" :for="fieldId" :class="['field-label', state]">
8+
{{ scaffold.label }}
9+
<sup v-if="required" class="required">*</sup>
10+
</label>
11+
12+
<div v-if="scaffold.description" class="description">
13+
{{ scaffold.description }}
14+
</div>
15+
16+
<component
17+
:is="map[fieldType]"
18+
:field="field"
19+
:form-scaffold="formScaffold"
20+
:disabled="disabled"
21+
@update-value="updateValue"
22+
@toggle-state="toggleState"
23+
@toggle-focused="handleFocus($event, toggleState)" />
24+
25+
<slot />
26+
27+
<div v-if="validationMessage" class="validation-message">
28+
{{ validationMessage }}
29+
</div>
30+
31+
</ZeroFormField>
32+
</template>
33+
34+
<script setup>
35+
// ======================================================================= Setup
36+
const props = defineProps({
37+
scaffold: {
38+
type: Object,
39+
required: true
40+
},
41+
/**
42+
* Used in the Array field
43+
*/
44+
formScaffold: {
45+
type: Object,
46+
required: false,
47+
default: () => {}
48+
},
49+
forceDisabled: {
50+
type: Boolean,
51+
required: false,
52+
default: false
53+
},
54+
forceValidate: {
55+
type: Boolean,
56+
required: false,
57+
default: true
58+
},
59+
/**
60+
* On occasions where the final root element in field-conditional.vue render
61+
* must be something specific. Such as when wrapping a <tbody> in a field-standalone,
62+
* it cannot be a div as the wrapper. It must be <tbody> at the root to prevent
63+
* SSR hydration errors.
64+
*/
65+
rootHtmlTag: {
66+
type: String,
67+
required: false,
68+
default: 'div'
69+
}
70+
})
71+
72+
const map = {
73+
'FieldInput': resolveComponent('FormFieldInput'),
74+
'FieldTextarea': resolveComponent('FormFieldTextarea'),
75+
'FieldBoolean': resolveComponent('FormFieldBoolean'),
76+
'FieldDatepicker': resolveComponent('FormFieldDatepicker'),
77+
'FieldSelect': resolveComponent('FormFieldSelect'),
78+
'FieldUpload': resolveComponent('FormFieldUpload')
79+
}
80+
81+
// ======================================================================== Data
82+
const focused = ref(false)
83+
84+
// ===================================================================== Methods
85+
const handleFocus = (state, toggleState) => {
86+
focused.value = state
87+
toggleState(state)
88+
}
89+
</script>
90+
91+
<style lang="scss" scoped>
92+
@keyframes grow {
93+
0% { transform: scale(0.5); }
94+
100% { opacity: 1; transform: scale(1); }
95+
}
96+
97+
// ///////////////////////////////////////////////////////////////////// General
98+
.field-container {
99+
&:hover,
100+
&:focus-within {
101+
.tooltip {
102+
&:before,
103+
&:after,
104+
.icon {
105+
transition: 150ms ease-in;
106+
opacity: 1;
107+
}
108+
&:before {
109+
transform: translate(0, -50%) rotate(-90deg);
110+
}
111+
&:after {
112+
transform: translate(0, -50%);
113+
}
114+
.icon {
115+
transform: scale(1);
116+
}
117+
}
118+
}
119+
&.disabled {
120+
.field-label {
121+
cursor: default;
122+
}
123+
}
124+
&:not(:last-child) {
125+
margin-bottom: toRem(30);
126+
}
127+
}
128+
129+
:deep(.field) {
130+
position: relative;
131+
font-weight: 500;
132+
}
133+
134+
:deep(.description) {
135+
margin-top: 0.5rem;
136+
line-height: leading(30, 18);
137+
margin-bottom: 2.25rem;
138+
}
139+
140+
// ////////////////////////////////////////////////////////////////////// Arrays
141+
.array {
142+
display: flex;
143+
flex-direction: column;
144+
}
145+
146+
.group {
147+
display: flex;
148+
flex-direction: row;
149+
.field {
150+
flex: 1;
151+
margin-bottom: 2rem;
152+
&:not(:last-child) {
153+
margin-right: 2rem;
154+
}
155+
}
156+
}
157+
158+
.add-group-button {
159+
margin-left: auto;
160+
}
161+
162+
.icon-trash {
163+
width: 1.5rem;
164+
}
165+
166+
// /////////////////////////////////////////////////////////////////////// Label
167+
.field-label {
168+
display: inline-block;
169+
margin-bottom: toRem(8);
170+
font-size: toRem(13);
171+
font-weight: 500;
172+
cursor: pointer;
173+
.required {
174+
color: $burntSienna;
175+
font-size: toRem(20);
176+
top: 0;
177+
}
178+
}
179+
180+
.panel-left {
181+
display: flex;
182+
flex-direction: row;
183+
align-items: center;
184+
}
185+
186+
.icon {
187+
display: none;
188+
margin-right: toRem(11);
189+
&.question-mark {
190+
display: block;
191+
}
192+
}
193+
194+
// ////////////////////////////////////////////////////////////////// Validation
195+
:deep(.validation-message) {
196+
margin-top: 0.5rem;
197+
font-size: 0.75rem;
198+
font-weight: 500;
199+
color: $burntSienna;
200+
font-style: italic;
201+
sup {
202+
top: -0.125rem;
203+
margin-right: 0.0625rem;
204+
font-size: 100%;
205+
}
206+
}
207+
</style>

components/form/field/boolean.vue

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<template>
2+
<ZeroFieldBoolean v-bind="{...props, ...$attrs}" />
3+
</template>
4+
5+
<script setup>
6+
// ======================================================================= Setup
7+
const props = defineProps({
8+
field: {
9+
type: Object,
10+
required: true
11+
},
12+
disabled: {
13+
type: Boolean,
14+
required: false,
15+
default: false
16+
}
17+
})
18+
</script>
19+
20+
<style lang="scss" scoped>
21+
// ///////////////////////////////////////////////////////////////////// General
22+
.field-boolean {
23+
display: flex;
24+
position: relative;
25+
width: toRem(29);
26+
height: toRem(15);
27+
border-radius: toRem(12);
28+
// background-color: $outerSpace;
29+
cursor: pointer;
30+
transition: 150ms ease-out;
31+
&:after {
32+
content: '';
33+
position: absolute;
34+
top: -1px;
35+
left: -1px;
36+
width: 15px;
37+
height: 15px;
38+
// background-color: $whisper;
39+
border: 1px solid rgba(#7D7C7D, 0.4);
40+
border-radius: 50%;
41+
pointer-events: none;
42+
transition: 150ms ease-out;
43+
}
44+
&:not(.disabled):not(.checked) {
45+
&:hover {
46+
// background-color: rgba($whisper, 0.2);
47+
}
48+
&.error {
49+
background-color: $burntSienna;
50+
&:after {
51+
border-color: $burntSienna;
52+
}
53+
}
54+
}
55+
&.disabled {
56+
cursor: no-drop;
57+
}
58+
&.checked {
59+
background-color: $chardonnay;
60+
&:after {
61+
transition: 150ms ease-in;
62+
border-color: rgba($chardonnay, 0.3);
63+
transform: translateX(100%);
64+
}
65+
}
66+
}
67+
68+
:deep(.boolean-container) {
69+
width: 100%;
70+
height: 100%;
71+
}
72+
73+
:deep(input.boolean) {
74+
display: block;
75+
width: 100%;
76+
height: 100%;
77+
opacity: 0;
78+
cursor: pointer;
79+
}
80+
</style>

0 commit comments

Comments
 (0)