Skip to content

Commit

Permalink
✨ Add creating person screen and flow
Browse files Browse the repository at this point in the history
  • Loading branch information
dmoyadev committed May 28, 2024
1 parent 8e47792 commit 6bd8cf1
Show file tree
Hide file tree
Showing 22 changed files with 769 additions and 65 deletions.
Binary file modified Design.sketch
Binary file not shown.
37 changes: 37 additions & 0 deletions src/components/avatar/BaseAvatar.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script setup lang="ts">
interface Props {
size?: number; /* The size of the avatar. @defaults: 40 */
src?: string; /* The source of the image. @defaults: undefined */
alt?: string; /* The alt text of the image. @defaults: undefined */
}
withDefaults(defineProps<Props>(), {
size: 40,
});
function getAvatarURL(size: number, name: string = '') {
const color1 = '77A3E3';
const color2 = '0F1939';
const colorsQuery = `colors=${color1},${color2},${color2},${color1},${color1}`;
return `https://source.boringavatars.com/beam/${size}/${name}?${colorsQuery}`;
}
</script>

<template>
<img
:src="src || getAvatarURL(40, alt)"
:alt="alt || src"
:width="size"
:height="size"
>
</template>

<style scoped lang="scss">
img {
border-radius: 50%;
width: v-bind('`${size}px`');
height: v-bind('`${size}px`');
object-fit: cover;
border: 2px solid var(--color-gray-0);
box-shadow: 0 4px 24px 0 rgba(0, 0, 0, .1);
}
</style>
6 changes: 3 additions & 3 deletions src/components/icon/BaseIcon.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ withDefaults(defineProps<Props>(), {
<template>
<iconify-icon
:icon="icon"
:width="size"
:height="size"
:width="`${size}px`"
:height="`${size}px`"
/>
</template>
</template>
9 changes: 5 additions & 4 deletions src/components/input/BaseInput.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import { ButtonForm, ButtonMode } from '@/components/button/BaseButton.types.ts'
import { hasSlotContent } from '@/utils/helpers.ts';
interface Props {
inputType?: InputType; /* The type of the input. @defaults InputForm.BLOCK */
form?: InputForm; /* The form of the input. @defaults InputForm.TEXT */
inputType?: InputType; /* The type of the input. @defaults InputForm.TEXT */
form?: InputForm; /* The form of the input. @defaults InputForm.BLOCK */
hasError?: boolean; /* Indicates if the input has an error. Determines if the `error` slot will be shown */
customValidity?: string; /* The error message of the input */
customValidity?: string; /* The error message of the input. It is the default value for the `error` slot */
loading?: boolean; /* Indicates if the input is loading */
isClearable?: boolean; /* Indicates if the input is clearable. @defaults true */
}
Expand Down Expand Up @@ -140,7 +140,7 @@ watch(() => props.hasError, (value) => {

<!-- Error slot -->
<p
v-if="!loading && hasError && hasSlotContent($slots.error)"
v-if="!loading && hasError"
class="error"
>
<!-- @slot Error message of the input. Defaults to the customValidity prop -->
Expand All @@ -162,6 +162,7 @@ watch(() => props.hasError, (value) => {

<style scoped lang="scss">
div {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-end;
Expand Down
40 changes: 40 additions & 0 deletions src/components/radio/BaseRadio.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<script setup lang="ts" generic="T">
const modelValue = defineModel<T>();
</script>

<template>
<label>
<input
v-model="modelValue"
v-bind="$attrs"
type="radio"
>

<span>
<!-- @slot The label of the radio -->
<slot />
</span>
</label>
</template>

<style scoped lang="scss">
label {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 42px;
border-radius: 4px;
color: var(--color-secondary-accent);
background: var(--color-gray-0-alpha);
&:has(input:checked) {
background: var(--color-primary);
color: var(--color-primary-accent);
}
&:has(input:disabled) {
opacity: .5;
}
}
</style>
121 changes: 121 additions & 0 deletions src/components/radio/BaseRadioGroup.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
<script setup lang="ts" generic="T">
import BaseRadio from '@/components/radio/BaseRadio.vue';
import { hasSlotContent } from '@/utils/helpers.ts';
interface Props {
options: T[]; /* The options of the radio group */
hasError?: boolean; /* Indicates if the radio group has an error */
customValidity?: string; /* The error message of the radio group. It is the default value for the `error` slot */
loading?: boolean; /* Indicates if the radio group is loading */
isDisabled?: (option: T) => boolean; /* Indicates if the radio group is disabled */
}
const props = defineProps<Props>();
const modelValue = defineModel<T>();
</script>

<template>
<div class="radio-group">
<div
class="radio-wrapper"
:class="{
'has-error': hasError,
}"
>
<!-- @slot The radio inputs -->
<slot>
<BaseRadio
v-for="(option, index) in options"
:key="index"
v-model="modelValue"
v-bind="$attrs"
:value="option"
:disabled="('disabled' in $attrs && (!!$attrs.disabled || $attrs.disabled === ''))
|| loading
|| (!!props.isDisabled && props.isDisabled(option))"
>
<!-- @slot Radio label -->
<slot
name="radio-label"
:option="option"
>
{{ option }}
</slot>
</BaseRadio>
</slot>
</div>

<!-- Error slot -->
<p
v-if="!loading && hasError"
class="error"
>
<!-- @slot Error message of the input. Defaults to the customValidity prop -->
<slot name="error">
{{ customValidity }}
</slot>
</p>

<!-- Helper slot -->
<p
v-if="hasSlotContent($slots.helper)"
class="helper"
>
<!-- @slot Helper message of the input -->
<slot name="helper" />
</p>
</div>
</template>

<style scoped lang="scss">
.radio-group {
width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-end;
gap: 4px;
.radio-wrapper {
display: flex;
align-items: center;
gap: 8px;
&.has-error {
border-color: var(--color-danger) !important;
animation: shake 0.2s ease-in-out 0s 2;
@keyframes shake {
0% {
margin-left: 0;
}
25% {
margin-left: 0.5rem;
}
75% {
margin-left: -0.5rem;
}
100% {
margin-left: 0;
}
}
}
}
.error {
color: var(--color-danger);
font-size: var(--font-size-small);
line-height: var(--font-size-small);
display: flex;
align-items: center;
gap: 4px;
}
.helper {
color: var(--color-primary);
font-size: var(--font-size-small);
line-height: var(--font-size-small);
display: flex;
align-items: center;
gap: 4px;
}
}
</style>
2 changes: 1 addition & 1 deletion src/components/select/BaseSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ function selectItem(item: T) {

<!-- Error slot -->
<p
v-if="!loading && hasError && hasSlotContent($slots.error)"
v-if="!loading && hasError"
class="error"
>
<!-- @slot Error message of the input. Defaults to the customValidity prop -->
Expand Down
4 changes: 2 additions & 2 deletions src/layouts/OnboardingLayout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { ButtonForm, ButtonMode } from '@/components/button/BaseButton.types.ts'
<BaseButton
:button-form="ButtonForm.INLINE"
:mode="ButtonMode.CLEAR"
to="/person/__new__"
to="/people/__new__"
>
Saltar
</BaseButton>
Expand All @@ -32,4 +32,4 @@ header {
padding: 8px 16px;
margin-bottom: 32px;
}
</style>
</style>
32 changes: 32 additions & 0 deletions src/modules/app/components/ErrorMessage.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<template>
<p>
<!-- @slot Error message -->
<slot />
</p>
</template>

<style scoped lang="scss">
p {
width: 100%;
background: var(--color-danger);
color: var(--color-danger-accent);
border-radius: 4px;
padding: 8px;
animation: shake 0.2s ease-in-out 0s 2;
@keyframes shake {
0% {
margin-left: 0;
}
25% {
margin-left: 0.5rem;
}
75% {
margin-left: -0.5rem;
}
100% {
margin-left: 0;
}
}
}
</style>
71 changes: 71 additions & 0 deletions src/modules/app/components/TheHeader.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<script setup lang="ts">
import BaseButton from '@/components/button/BaseButton.vue';
import BaseIcon from '@/components/icon/BaseIcon.vue';
import { ButtonForm, ButtonMode } from '@/components/button/BaseButton.types.ts';
defineProps<{
title?: string; /* The title of the page */
disallowBack?: boolean; /* Whether the back button should be hidden */
backPage?: string; /* The page to go back to */
actionText?: string; /* The text of the main action button */
actionForm?: string; /* The id of the form that the main action button should submit */
disableAction?: boolean; /* Whether the main action button should be disabled */
}>();
defineEmits<{
clickAction: []; /* Emitted when the action button is clicked */
}>();
</script>

<template>
<header>
<BaseButton
v-if="!disallowBack"
:mode="ButtonMode.CLEAR"
:button-form="ButtonForm.CIRCLE"
:to="backPage"
class="btn-back"
@click="!backPage && $router.back()"
>
<BaseIcon icon="mdi:arrow-left" />
</BaseButton>

<h1 v-if="title">
{{ title }}
</h1>

<BaseButton
v-if="actionText"
:mode="ButtonMode.CLEAR"
:button-form="ButtonForm.INLINE"
:disabled="disableAction"
:form="actionForm"
class="btn-action"
@click="() => $emit('clickAction')"
>
{{ actionText }}
</BaseButton>
</header>
</template>

<style scoped lang="scss">
header {
display: flex;
align-items: center;
padding: 8px;
gap: 8px;
&:not(:has(.btn-back)) {
padding-left: 16px;
}
h1 {
text-transform: uppercase;
font-size: var(--font-size-small);
}
.btn-action {
margin-left: auto;
}
}
</style>
Loading

0 comments on commit 6bd8cf1

Please sign in to comment.