Skip to content

Commit

Permalink
Initial pass on login re-work:
Browse files Browse the repository at this point in the history
* Added generic modal component
* Adjusted TextInput to delay invalid styling until first interaction
* Added help message to TextInput
* Adjust login screen to use generic modal over a home-view component
  • Loading branch information
MelissaAutumn committed Sep 6, 2024
1 parent 43e5993 commit bb75375
Show file tree
Hide file tree
Showing 5 changed files with 392 additions and 133 deletions.
7 changes: 6 additions & 1 deletion frontend/src/assets/styles/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,9 @@
}

/* Force First Time User Experience to be different from the rest of the site */
.page-ftue {
.page-ftue, .new-design {
font-family: 'Inter', 'sans-serif';
font-size: 0.8125rem;
}


Expand All @@ -199,3 +200,7 @@
display: block;
}
}

.modal-active {
overflow: hidden;
}
228 changes: 228 additions & 0 deletions frontend/src/components/GenericModal.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
<script setup lang="ts">
import {
onMounted, inject, ref, toRefs, onUnmounted,
} from 'vue';
import { refreshKey } from '@/keys';
import NoticeBar from '@/tbpro/elements/NoticeBar.vue';
// component properties
interface Props {
infoMessage?: string;
warningMessage?: string;
errorMessage?: string;
}
const props = withDefaults(defineProps<Props>(), {
infoMessage: null,
warningMessage: null,
errorMessage: null,
});
const { infoMessage, warningMessage, errorMessage } = toRefs(props);
const refresh = inject(refreshKey);
onMounted(async () => {
// Activate page scroll-lock
window.document.body.classList.add('modal-active');
await refresh();
});
onUnmounted(() => {
// Release page scroll-lock
window.document.body.classList.remove('modal-active');
});
</script>

<template>
<div class="new-design overlay" role="dialog" tabindex="-1" aria-labelledby="title" aria-modal="true">
<div class="modal">
<div class="modal-header">
<slot name="header"></slot>
<notice-bar type="error" v-if="errorMessage">
{{ errorMessage }}
</notice-bar>
<notice-bar type="warning" v-else-if="warningMessage">
{{ warningMessage }}
</notice-bar>
<notice-bar v-else-if="infoMessage">
{{ infoMessage }}
</notice-bar>
<div class="pls-keep-height" v-else/>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-actions">
<slot name="actions"></slot>
</div>
<div class="divider"></div>
<div class="footer">
<slot name="footer"></slot>
</div>
</div>
</div>
</template>
<style scoped>
@import '@/assets/styles/custom-media.pcss';
@import '@/assets/styles/mixins.pcss';
.overlay {
@mixin faded-background var(--colour-neutral-900);
position: fixed;
display: flex;
left: 0;
top: 0;
z-index: 55;
width: 100vw;
height: 100vh;
overflow: hidden;
align-items: center;
justify-content: center;
}
.modal-body {
display: flex;
width: 100%;
flex-direction: column;
align-items: center;
margin-top: 1rem;
}
.modal-actions {
display: flex;
width: 100%;
gap: 1rem;
justify-content: center;
margin-top: 2rem;
}
.modal-header {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 8.0rem;
width: 100%;
gap: 1rem;
}
/* Empty space if a notice bar isn't shown */
.pls-keep-height {
min-height: 2.0625rem; /* 33px */
}
/* position-center apmt-background-color fixed z-[60] flex size-full gap-6 rounded-xl bg-white p-8 pb-0 drop-shadow-xl*/
.modal {
--background-color: var(--colour-neutral-raised);
--background: url('@/assets/svg/ftue-background.svg');
position: relative;
width: 100%;
height: 100%;
background-color: var(--background-color);
background-image: var(--background);
background-size: cover;
background-repeat: no-repeat;
border-radius: 0.75rem;
padding: 1rem 1rem 0;
overflow-y: scroll;
overflow-x: hidden;
}
.dark .modal {
--background: url('@/assets/svg/ftue-background-dark.svg');
}
.modal::before {
content: '';
position: absolute;
inset: -4px;
margin: 50% 5% 0;
opacity: 0.8;
z-index: -1;
border-radius: 9px;
background: linear-gradient(119deg, #A3ECE3 -1.91%, #03AFD7 48.8%, #008080 100.54%);
filter: blur(30px);
}
.dark {
.modal::before {
background: linear-gradient(119deg, #0B8C86 -1.91%, #1C6395 100.54%);
}
}
.divider {
width: 100%;
padding-bottom: 1px;
border-radius: unset;
background: linear-gradient(90deg, rgba(21, 66, 124, 0) 20.5%, rgba(21, 66, 124, 0.2) 50%, rgba(21, 66, 124, 0) 79.5%);
}
.dark {
.divider {
background: linear-gradient(90deg, rgba(255, 255, 255, 0.00) 0%, rgba(255, 255, 255, 0.40) 50%, rgba(255, 255, 255, 0.00) 100%);
}
}
.footer {
bottom: 0;
height: 4rem;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
padding-bottom: 1rem;
gap: 1rem;
:deep(a) {
color: var(--colour-service-primary-pressed);
font-size: 0.75rem;
line-height: 1.5rem;
}
}
/* Default styling for #title */
:deep(#title) {
color: var(--colour-ti-base);
font-family: 'Inter', 'sans-serif';
font-weight: 400;
font-size: 1.375rem;
line-height: 1.664rem;
}
@media (--md) {
.modal-header {
margin-bottom: 0;
}
.modal {
width: 50rem; /* 800px */
height: 37.5rem; /* 600px */
padding: 2rem 2rem 0;
overflow: visible;
}
.modal-body {
height: 15.0rem;
}
.modal-actions {
justify-content: center;
position: absolute;
left: 0;
bottom: 5.75rem;
margin: 0;
}
.divider {
position: absolute;
bottom: 4rem;
width: 50rem;
height: 0.0625rem;
}
.footer {
position: absolute;
left: 0;
padding-bottom: 0;
}
}
</style>
2 changes: 1 addition & 1 deletion frontend/src/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@
"home": "Home",
"immediately": "instant",
"inPerson": "In person",
"inviteCode": "Have an invite code?",
"inviteCode": "Invite code",
"language": "Choose language",
"legal": "Legal",
"light": "Light",
Expand Down
47 changes: 34 additions & 13 deletions frontend/src/tbpro/elements/TextInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
import { ref } from 'vue';
import { HTMLInputElementEvent } from '@/models';
const model = defineModel<string>();
const isInvalid = ref(false);
const validationMessage = ref('');
const isDirty = ref(false);
const inputRef = ref<HTMLInputElement>(null);
/**
* Forwards focus intent to the text input element.
Expand All @@ -17,29 +21,38 @@ const focus = () => {
// component properties
interface Props {
name: string;
help?: string;
remoteError?: string;
type?: string;
placeholder?: string;
required?: boolean;
disabled?: boolean;
};
}
withDefaults(defineProps<Props>(), {
type: 'text',
help: null,
remoteError: null,
placeholder: '',
required: false,
disabled: false,
})
});
defineEmits(['submit']);
defineExpose({ focus });
const model = defineModel<string>();
const isInvalid = ref(false);
const validationMessage = ref('');
const onInvalid = (evt: HTMLInputElementEvent) => {
isInvalid.value = true;
isDirty.value = true;
validationMessage.value = evt.target.validationMessage;
};
/**
* On any change we mark the element as dirty
* this is so we can delay :invalid until
* the user does something worth invalidating
*/
const onChange = () => {
isDirty.value = true;
};
</script>

<template>
Expand All @@ -51,18 +64,26 @@ const onInvalid = (evt: HTMLInputElementEvent) => {
<input
class="tbpro-input"
v-model="model"
:class="{'dirty': isDirty}"
:type="type"
:id="name"
:name="name"
:disabled="disabled"
:placeholder="placeholder"
:required="required"
@invalid="onInvalid"
@change="onChange"
ref="inputRef"
/>
<span :class="{'visible': isInvalid}" class="help-label">
<span v-if="isInvalid" class="help-label invalid">
{{ validationMessage }}
</span>
<span v-else-if="help" class="help-label">
{{ help }}
</span>
<span v-else class="help-label">
<!-- Empty space -->
</span>
</label>
</template>
Expand All @@ -86,18 +107,18 @@ const onInvalid = (evt: HTMLInputElementEvent) => {
}
.help-label {
visibility: hidden;
display: flex;
color: var(--colour-danger-default);
color: var(--colour-ti-base);
width: 100%;
min-height: 0.9375rem;
font-size: 0.625rem;
line-height: 0.9375rem;
}
padding: 0.1875rem;
.visible {
visibility: visible;
&.invalid {
color: var(--colour-danger-default);
}
}
.required {
Expand All @@ -124,7 +145,7 @@ const onInvalid = (evt: HTMLInputElementEvent) => {
border-radius: 0.125rem;
}
&:invalid {
&.dirty:invalid {
--colour-btn-border: var(--colour-ti-critical);
}
Expand Down
Loading

0 comments on commit bb75375

Please sign in to comment.