Add initial work on fancy form components for onboarding
This commit is contained in:
parent
37244d3dff
commit
b312ac68c8
8 changed files with 338 additions and 79 deletions
29
packages/bbui/src/FancyForm/FancyButton.svelte
Normal file
29
packages/bbui/src/FancyForm/FancyButton.svelte
Normal 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>
|
72
packages/bbui/src/FancyForm/FancyField.svelte
Normal file
72
packages/bbui/src/FancyForm/FancyField.svelte
Normal 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>
|
40
packages/bbui/src/FancyForm/FancyForm.svelte
Normal file
40
packages/bbui/src/FancyForm/FancyForm.svelte
Normal 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>
|
99
packages/bbui/src/FancyForm/FancyInput.svelte
Normal file
99
packages/bbui/src/FancyForm/FancyInput.svelte
Normal 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>
|
3
packages/bbui/src/FancyForm/index.js
Normal file
3
packages/bbui/src/FancyForm/index.js
Normal 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"
|
|
@ -101,3 +101,6 @@ export { banner, BANNER_TYPES } from "./Stores/banner"
|
|||
|
||||
// Helpers
|
||||
export * as Helpers from "./helpers"
|
||||
|
||||
// Fancy form components
|
||||
export * from "./FancyForm"
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue