1
0
Fork 0
mirror of synced 2024-08-27 07:51:37 +12:00

Add initial work on fancy form components for onboarding

This commit is contained in:
Andrew Kingston 2023-01-17 09:58:29 +00:00
parent 37244d3dff
commit b312ac68c8
8 changed files with 338 additions and 79 deletions

View file

@ -0,0 +1,29 @@
<script>
import Icon from "../Icon/Icon.svelte"
import FancyField from "./FancyField.svelte"
export let icon
export let disabled
</script>
<FancyField on:click clickable {disabled}>
{#if icon}
{#if icon.includes("/")}
<img src={icon} alt="button" />
{:else}
<Icon name={icon} />
{/if}
{/if}
<div>
<slot />
</div>
</FancyField>
<style>
img {
width: 22px;
}
div {
font-size: var(--font-size-l);
}
</style>

View file

@ -0,0 +1,72 @@
<script>
export let disabled = false
export let error = null
export let focused = false
export let clickable = false
</script>
<div
class="fancy-field"
class:error
class:disabled
class:focused
class:clickable
>
<div class="content" on:click>
<slot />
</div>
{#if error}
<div class="error-message">
{error}
</div>
{/if}
</div>
<style>
.fancy-field {
max-width: 400px;
background: var(--spectrum-global-color-gray-75);
border: 1px solid var(--spectrum-global-color-gray-300);
border-radius: 4px;
box-sizing: border-box;
transition: border-color 130ms ease-out, background 130ms ease-out,
background 130ms ease-out;
color: var(--spectrum-global-color-gray-800);
}
.fancy-field:hover {
border-color: var(--spectrum-global-color-gray-400);
}
.fancy-field.clickable:hover {
background: var(--spectrum-global-color-gray-200);
cursor: pointer;
}
.fancy-field.focused {
border-color: var(--spectrum-global-color-blue-400);
}
.fancy-field.error {
border-color: var(--spectrum-global-color-red-400);
}
.fancy-field.disabled {
background: var(--spectrum-global-color-gray-200);
color: var(--spectrum-global-color-gray-400);
border: 1px solid var(--spectrum-global-color-gray-300);
pointer-events: none;
}
.content {
height: 64px;
padding: 0 16px;
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: 16px;
position: relative;
}
.error-message {
background: var(--spectrum-global-color-red-400);
color: white;
font-size: 14px;
padding: 6px 16px;
font-weight: 500;
}
</style>

View file

@ -0,0 +1,40 @@
<script>
import { setContext } from "svelte"
let fields = {}
setContext("fancy-form", {
registerField: (id, api) => {
fields = { ...fields, [id]: api }
},
unregisterField: id => {
delete fields[id]
fields = fields
},
})
export const validate = () => {
let valid = true
Object.values(fields).forEach(api => {
if (!api.validate()) {
valid = false
}
})
return valid
}
</script>
<div class="fancy-form">
<slot />
</div>
<style>
.fancy-form :global(.fancy-field:not(:first-child)) {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
.fancy-form :global(.fancy-field:not(:last-child)) {
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
}
</style>

View file

@ -0,0 +1,99 @@
<script>
import { createEventDispatcher, getContext, onMount } from "svelte"
import FancyField from "./FancyField.svelte"
export let label
export let value
export let type = "text"
export let disabled = false
export let error = null
export let validate = null
const dispatch = createEventDispatcher()
const formContext = getContext("fancy-form")
const id = Math.random()
let focused = false
$: placeholder = !focused && !value
const onChange = e => {
const newValue = e.target.value
dispatch("change", newValue)
value = newValue
if (validate) {
error = validate(newValue)
}
}
const API = {
validate: () => {
if (validate) {
error = validate(value)
} else {
error = null
}
return !error
},
}
onMount(() => {
if (formContext) {
formContext.registerField(id, API)
}
return () => {
if (formContext) {
formContext.unregisterField(id)
}
}
})
</script>
<FancyField {error} {disabled} {focused}>
{#if label}
<div class="label" class:placeholder>
{label}
</div>
{/if}
<input
{disabled}
value={value || ""}
type={type || "text"}
on:input={onChange}
on:focus={() => (focused = true)}
on:blur={() => (focused = false)}
class:placeholder
/>
</FancyField>
<style>
.label {
font-size: 14px;
font-weight: 500;
transform: translateY(-12px);
position: absolute;
color: var(--spectrum-global-color-gray-600);
transition: font-size 130ms ease-out, transform 130ms ease-out;
}
.label.placeholder {
font-size: 15px;
transform: translateY(0);
}
input {
z-index: 1;
background: transparent;
font-size: 15px;
color: var(--spectrum-global-color-gray-900);
outline: none;
border: none;
transition: transform 130ms ease-out;
width: 100%;
transform: translateY(9px);
}
input.placeholder {
transform: translateY(0);
position: absolute;
top: 0;
left: 0;
height: 100%;
}
</style>

View file

@ -0,0 +1,3 @@
export { default as FancyInput } from "./FancyInput.svelte"
export { default as FancyButton } from "./FancyButton.svelte"
export { default as FancyForm } from "./FancyForm.svelte"

View file

@ -101,3 +101,6 @@ export { banner, BANNER_TYPES } from "./Stores/banner"
// Helpers
export * as Helpers from "./helpers"
// Fancy form components
export * from "./FancyForm"

View file

@ -1,40 +1,30 @@
<script>
import { ActionButton } from "@budibase/bbui"
import { FancyButton } from "@budibase/bbui"
import GoogleLogo from "assets/google-logo.png"
import { auth, organisation } from "stores/portal"
let show
$: tenantId = $auth.tenantId
$: show = $organisation.google
$: show = true //$organisation.google
</script>
{#if show}
<ActionButton
on:click={() =>
window.open(`/api/global/auth/${tenantId}/google`, "_blank")}
<FancyButton
on:click={() => {
window.open(`/api/global/auth/${tenantId}/google`, "_blank")
}}
icon={GoogleLogo}
>
<div class="inner">
<img src={GoogleLogo} alt="google icon" />
<p>Sign in with Google</p>
</div>
</ActionButton>
Log in with Google
</FancyButton>
<FancyButton
disabled
on:click={() => {
window.open(`/api/global/auth/${tenantId}/google`, "_blank")
}}
icon={GoogleLogo}
>
Log in with Google
</FancyButton>
{/if}
<style>
.inner {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
padding-top: var(--spacing-xs);
padding-bottom: var(--spacing-xs);
}
.inner img {
width: 18px;
margin: 3px 10px 3px 3px;
}
.inner p {
margin: 0;
}
</style>

View file

@ -5,11 +5,12 @@
Button,
Divider,
Heading,
Input,
Layout,
notifications,
Link,
} from "@budibase/bbui"
import { FancyInput, FancyForm } from "@budibase/bbui"
import { TestimonialPage } from "@budibase/frontend-core"
import { goto } from "@roxi/routify"
import { auth, organisation, oidc, admin } from "stores/portal"
import GoogleButton from "./_components/GoogleButton.svelte"
@ -20,12 +21,16 @@
let username = ""
let password = ""
let loaded = false
let form
$: company = $organisation.company || "Budibase"
$: multiTenancyEnabled = $admin.multiTenancy
$: cloud = $admin.cloud
async function login() {
if (!form.validate()) {
return
}
try {
await auth.login({
username: username.trim(),
@ -57,60 +62,78 @@
</script>
<svelte:window on:keydown={handleKeydown} />
<div class="login">
<div class="main">
<Layout>
<Layout noPadding justifyItems="center">
<img alt="logo" src={$organisation.logoUrl || Logo} />
<Heading textAlign="center">Sign in to {company}</Heading>
</Layout>
{#if loaded}
<GoogleButton />
<OIDCButton oidcIcon={$oidc.logo} oidcName={$oidc.name} />
{/if}
<Divider noGrid />
<Layout gap="XS" noPadding>
<Body size="S" textAlign="center">Sign in with email</Body>
<Input label="Email" bind:value={username} />
<Input
label="Password"
type="password"
on:change
bind:value={password}
/>
</Layout>
<Layout gap="XS" noPadding>
<Button cta disabled={!username && !password} on:click={login}
>Sign in to {company}</Button
<TestimonialPage>
<Layout noPadding>
<Layout noPadding justifyItems="center">
<img alt="logo" src={$organisation.logoUrl || Logo} />
<Heading textAlign="center">Log in to {company}</Heading>
</Layout>
{#if loaded}
<GoogleButton />
<OIDCButton oidcIcon={$oidc.logo} oidcName={$oidc.name} />
{/if}
<Divider />
<FancyForm bind:this={form}>
<FancyInput
validate={x => !x && "Please enter your work email"}
label="Your work email"
value={username}
on:change={e => (username = e.detail)}
/>
<FancyInput
disabled
label="Work email"
value={username}
on:change={e => (username = e.detail)}
/>
<FancyInput
label="Work email"
value={username}
on:change={e => (username = e.detail)}
/>
<FancyInput
validate={x => !x && "Please enter your password"}
label="Password"
type="password"
value={password}
on:change={e => (password = e.detail)}
/>
</FancyForm>
<Layout gap="XS" noPadding justifyItems="center">
<div>
<Button cta on:click={login}>
Log in to {company}
</Button>
</div>
<ActionButton quiet on:click={() => $goto("./forgot")}>
Forgot password?
</ActionButton>
{#if multiTenancyEnabled && !cloud}
<ActionButton
quiet
on:click={() => {
admin.unload()
$goto("./org")
}}
>
<ActionButton quiet on:click={() => $goto("./forgot")}>
Forgot password?
Change organisation
</ActionButton>
{#if multiTenancyEnabled && !cloud}
<ActionButton
quiet
on:click={() => {
admin.unload()
$goto("./org")
}}
>
Change organisation
</ActionButton>
{/if}
</Layout>
{#if cloud}
<Body size="xs" textAlign="center">
By using Budibase Cloud
<br />
you are agreeing to our
<Link href="https://budibase.com/eula" target="_blank"
>License Agreement</Link
>
</Body>
{/if}
</Layout>
</div>
</div>
{#if cloud}
<Body size="xs" textAlign="center">
By using Budibase Cloud
<br />
you are agreeing to our
<Link href="https://budibase.com/eula" target="_blank">
License Agreement
</Link>
</Body>
{/if}
</Layout>
</TestimonialPage>
<style>
.login {