feat(ui): form layout

This commit is contained in:
upsiflu 2024-12-30 11:50:57 +01:00
parent 2ebda850c7
commit 8ff2c05e05
7 changed files with 78 additions and 124 deletions

View File

@ -80,7 +80,7 @@ const submit = async () => {
</script> </script>
<template> <template>
<form <Layout form stack
style="max-width: 600px" style="max-width: 600px"
@submit.prevent="submit()" @submit.prevent="submit()"
> >
@ -107,7 +107,6 @@ const submit = async () => {
</Alert> </Alert>
<Spacer v-if="errors.length > 0" /> <Spacer v-if="errors.length > 0" />
<template v-if="domain === store.getters['instance/domain']"> <template v-if="domain === store.getters['instance/domain']">
<div style="margin-bottom: 16px">
<Input <Input
id="username-field" id="username-field"
ref="username" ref="username"
@ -128,8 +127,6 @@ const submit = async () => {
</template> </template>
</template> </template>
</Input> </Input>
</div>
<div style="margin-bottom: 16px">
<Input password <Input password
v-model="credentials.password" v-model="credentials.password"
field-id="password-field" field-id="password-field"
@ -146,7 +143,6 @@ const submit = async () => {
</router-link> </router-link>
</template> </template>
</Input> </Input>
</div>
</template> </template>
<template v-else> <template v-else>
<p> <p>
@ -161,5 +157,5 @@ const submit = async () => {
> >
{{ t('components.auth.LoginForm.button.login') }} {{ t('components.auth.LoginForm.button.login') }}
</Button> </Button>
</form> </Layout>
</template> </template>

View File

@ -106,7 +106,7 @@ fetchInstanceSettings()
:show-signup="false" :show-signup="false"
/> />
</div> </div>
<form <Layout form stack
v-else v-else
style="max-width: 600px" style="max-width: 600px"
@submit.prevent="submit()" @submit.prevent="submit()"
@ -116,20 +116,17 @@ fetchInstanceSettings()
> >
{{ t('components.auth.SignupForm.message.registrationClosed') }} {{ t('components.auth.SignupForm.message.registrationClosed') }}
</Alert> </Alert>
<Spacer v-if="!store.state.instance.settings.users.registration_enabled.value"/>
<Alert yellow <Alert yellow
v-else-if="signupRequiresApproval" v-else-if="signupRequiresApproval"
> >
{{ t('components.auth.SignupForm.message.requiresReview') }} {{ t('components.auth.SignupForm.message.requiresReview') }}
</Alert> </Alert>
<Spacer v-else-if="signupRequiresApproval" />
<template v-if="formCustomization?.help_text"> <template v-if="formCustomization?.help_text">
<rendered-description <rendered-description
:content="formCustomization.help_text" :content="formCustomization.help_text"
:fetch-html="fetchDescriptionHtml" :fetch-html="fetchDescriptionHtml"
:permissive="true" :permissive="true"
/> />
<div class="ui hidden divider" />
</template> </template>
<Alert red <Alert red
v-if="errors.length > 0" v-if="errors.length > 0"
@ -146,7 +143,6 @@ fetchInstanceSettings()
</li> </li>
</ul> </ul>
</Alert> </Alert>
<div class="required field">
<Input <Input
id="username-field" id="username-field"
:label = "t('components.auth.SignupForm.label.username')" :label = "t('components.auth.SignupForm.label.username')"
@ -158,8 +154,6 @@ fetchInstanceSettings()
autofocus autofocus
:placeholder="labels.usernamePlaceholder" :placeholder="labels.usernamePlaceholder"
/> />
</div>
<div class="required field">
<Input <Input
:label="t('components.auth.SignupForm.label.email')" :label="t('components.auth.SignupForm.label.email')"
id="email-field" id="email-field"
@ -170,19 +164,12 @@ fetchInstanceSettings()
type="email" type="email"
:placeholder="labels.emailPlaceholder" :placeholder="labels.emailPlaceholder"
/> />
</div>
<div class="required field">
<Input password <Input password
:label="t('components.auth.SignupForm.label.password')" :label="t('components.auth.SignupForm.label.password')"
v-model="payload.password1" v-model="payload.password1"
field-id="password-field" field-id="password-field"
/> />
</div> <Input v-if="!store.state.instance.settings.users.registration_enabled.value"
<div
v-if="!store.state.instance.settings.users.registration_enabled.value"
class="required field"
>
<Input
:label="t('components.auth.SignupForm.label.invitation')" :label="t('components.auth.SignupForm.label.invitation')"
id="invitation-code" id="invitation-code"
v-model="payload.invitation" v-model="payload.invitation"
@ -191,14 +178,11 @@ fetchInstanceSettings()
name="invitation" name="invitation"
:placeholder="labels.placeholder" :placeholder="labels.placeholder"
/> />
</div> <div v-if="signupRequiresApproval && (formCustomization?.fields.length ?? 0) > 0"
<template v-if="signupRequiresApproval && (formCustomization?.fields.length ?? 0) > 0">
<div
v-for="(field, idx) in formCustomization?.fields" v-for="(field, idx) in formCustomization?.fields"
:key="idx" :key="idx"
:class="[{required: field.required}, 'field']" :class="[{required: field.required}, 'field']"
> >
<!-- TODO: Add `label` prop to `Textarea` component -->
<Textarea <Textarea
:label="field.label" :label="field.label"
v-if="field.input_type === 'long_text'" v-if="field.input_type === 'long_text'"
@ -216,8 +200,6 @@ fetchInstanceSettings()
:required="field.required" :required="field.required"
/> />
</div> </div>
</template>
<Spacer />
<Button <Button
primary primary
auto auto
@ -225,5 +207,5 @@ fetchInstanceSettings()
> >
{{ t('components.auth.SignupForm.button.create') }} {{ t('components.auth.SignupForm.button.create') }}
</Button> </Button>
</form> </Layout>
</template> </template>

View File

@ -107,12 +107,6 @@ onMounted(() => {
padding: 10px; padding: 10px;
} }
// Margin
&+.button {
margin-left: 8px;
}
border-radius: var(--fw-border-radius); border-radius: var(--fw-border-radius);
transform: translateX(var(--fw-translate-x)) translateY(var(--fw-translate-y)) scale(var(--fw-scale)); transform: translateX(var(--fw-translate-x)) translateY(var(--fw-translate-y)) scale(var(--fw-scale));

View File

@ -5,14 +5,16 @@ const props = defineProps<{
noRule?:true, noRule?:true,
noWrap?:true noWrap?:true
} }
& { [P in "stack" | "grid" | "flex" | "columns"]?: true | string } & { [P in "stack" | "grid" | "flex" | "columns" | "row" | "page"]?: true | string }
& { [C in "nav" | "aside" | "header" | "footer" | "main" | "label" | "h1" | "h2" | "h3" | "h4" | "h5"]?:true }>() & { [C in "nav" | "aside" | "header" | "footer" | "main" | "label" | "form" | "h1" | "h2" | "h3" | "h4" | "h5"]?:true }>()
const columnWidth = props.columnWidth ?? 46 const columnWidth = props.columnWidth ?? 46
</script> </script>
<template> <template>
<component <component
:is="props.nav ? 'nav' : props.aside ? 'aside' : props.header ? 'header' : props.footer ? 'footer' : props.main ? 'main' : props.label ? 'label' : props.h1? 'h1' : props.h2? 'h2' : props.h3? 'h3' : props.h4? 'h4' : props.h5? 'h5' : 'div'" :is="props.nav ? 'nav' : props.aside ? 'aside' : props.header ? 'header' : props.footer ? 'footer' : props.main ? 'main' : props.label ? 'label' : props.form ? 'form' : props.h1? 'h1' : props.h2? 'h2' : props.h3? 'h3' : props.h4? 'h4' : props.h5? 'h5' : 'div'"
:class="[ :class="[
$style.layout, $style.layout,
noGap || $style.gap, noGap || $style.gap,

View File

@ -61,6 +61,7 @@
} }
> .label { > .label {
margin-top: -18px;
padding-bottom: 8px; padding-bottom: 8px;
font-size:14px; font-size:14px;
font-weight:600; font-weight:600;

View File

@ -8,6 +8,7 @@ import { useRouter } from 'vue-router'
import Input from '~/components/ui/Input.vue' import Input from '~/components/ui/Input.vue'
import Button from '~/components/ui/Button.vue' import Button from '~/components/ui/Button.vue'
import Spacer from '~/components/ui/layout/Spacer.vue' import Spacer from '~/components/ui/layout/Spacer.vue'
import Layout from '~/components/ui/Layout.vue'
import axios from 'axios' import axios from 'axios'
@ -55,7 +56,7 @@ onMounted(() => emailInput.value.focus())
<h2> <h2>
{{ t('views.auth.PasswordReset.header.reset') }} {{ t('views.auth.PasswordReset.header.reset') }}
</h2> </h2>
<form <Layout form stack
@submit.prevent="submit()" @submit.prevent="submit()"
style="max-width: 600px" style="max-width: 600px"
> >
@ -79,7 +80,6 @@ onMounted(() => emailInput.value.focus())
<p> <p>
{{ t('views.auth.PasswordReset.help.form') }} {{ t('views.auth.PasswordReset.help.form') }}
</p> </p>
<div class="field">
<Input <Input
:label="t('views.auth.PasswordReset.label.email')" :label="t('views.auth.PasswordReset.label.email')"
id="account-email" id="account-email"
@ -91,8 +91,7 @@ onMounted(() => emailInput.value.focus())
autofocus autofocus
:placeholder="labels.placeholder" :placeholder="labels.placeholder"
/> />
</div> <Layout flex>
<Spacer />
<Button <Button
:class="['ui', {'loading': isLoading}, 'success', 'button']" :class="['ui', {'loading': isLoading}, 'success', 'button']"
type="submit" type="submit"
@ -108,6 +107,7 @@ onMounted(() => emailInput.value.focus())
> >
{{ t('views.auth.PasswordReset.link.back') }} {{ t('views.auth.PasswordReset.link.back') }}
</Button> </Button>
</form> </Layout>
</Layout>
</main> </main>
</template> </template>

View File

@ -3,6 +3,7 @@ import Input from "~/components/ui/Input.vue"
import Button from "~/components/ui/Button.vue" import Button from "~/components/ui/Button.vue"
import Layout from "~/components/ui/Layout.vue" import Layout from "~/components/ui/Layout.vue"
import Spacer from "~/components/ui/layout/Spacer.vue" import Spacer from "~/components/ui/layout/Spacer.vue"
import Alert from "~/components/ui/Alert.vue"
</script> </script>
# Input # Input
@ -90,54 +91,32 @@ You can add a template on the right-hand side of the input to guide the user's i
### Password ### Password
```vue-html ```vue-html
<Layout flex no-gap style="align-items:flex-end"> <Spacer :size="64" />
<Spacer v <Layout form stack>
:size="80" <Input label="User name" />
style="align-self: baseline;" <Input password label="Password" />
/> <Layout flex>
<Input> <Button primary> Submit </Button>
<template #label> <Button> Cancel </Button>
User name
</template>
</Input>
</Layout> </Layout>
<Layout flex no-gap style="align-items:flex-end">
<Spacer v
:size="80"
style="align-self: baseline;"
/>
<Input password>
<template #label>
Password
</template>
</Input>
</Layout> </Layout>
``` ```
<!-- Implement a component for baseline alignments like this --> <Spacer :size="64" />
<Layout form stack>
<Input label="User name" />
<Input password label="Password" />
<Layout flex>
<Button primary> Submit </Button>
<Button> Cancel </Button>
</Layout>
</Layout>
<Layout flex no-gap style="align-items:flex-end"> ::: tip
<Spacer v
:size="80" We use the spacer to simulate the baseline alignment on page layouts (64px between sections)
style="align-self: baseline;"
/> :::
<Input>
<template #label>
User name
</template>
</Input>
</Layout>
<Layout flex no-gap style="align-items:flex-end">
<Spacer v
:size="80"
style="align-self: baseline;"
/>
<Input password>
<template #label>
Password
</template>
</Input>
</Layout>
## Fallthrough attributes ## Fallthrough attributes