Add initial work on new Form component, spectrum styles and new text field

This commit is contained in:
Andrew Kingston 2021-01-26 08:55:44 +00:00
parent 0060601118
commit 06c5a0f717
13 changed files with 376 additions and 42 deletions

View File

@ -7,8 +7,7 @@
"name": "Form",
"icon": "ri-file-edit-line",
"children": [
"dataform",
"dataformwide",
"form",
"input",
"richtext",
"datepicker"

View File

@ -1362,11 +1362,6 @@ minimatch@^3.0.4:
dependencies:
brace-expansion "^1.1.7"
mustache@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.0.1.tgz#d99beb031701ad433338e7ea65e0489416c854a2"
integrity sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA==
nanoid@^2.1.0:
version "2.1.11"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-2.1.11.tgz#ec24b8a758d591561531b4176a01e3ab4f0f0280"

View File

@ -115,33 +115,54 @@
}
]
},
"dataform": {
"form": {
"name": "Form",
"icon": "ri-file-edit-line",
"styleable": true
},
"dataformwide": {
"name": "Wide Form",
"icon": "ri-file-edit-line",
"styleable": true
},
"input": {
"name": "Text Field",
"description": "A textfield component that allows the user to input text.",
"icon": "ri-edit-box-line",
"styleable": true,
"bindable": true,
"hasChildren": true,
"dataProvider": true,
"datasourceSetting": "datasource",
"settings": [
{
"type": "text",
"label": "Label",
"key": "label"
"type": "datasource",
"label": "Data",
"key": "datasource"
},
{
"label": "Type",
"key": "type",
"defaultValue": "text",
"options": ["text", "password"]
"type": "select",
"label": "Theme",
"key": "theme",
"defaultValue": "spectrum--light",
"options": [
{
"label": "Light",
"value": "spectrum--light"
},
{
"label": "Dark",
"value": "spectrum--dark"
},
{
"label": "Darkest",
"value": "spectrum--darkest"
}
]
},
{
"type": "select",
"label": "Size",
"key": "size",
"defaultValue": "spectrum--medium",
"options": [
{
"label": "Medium",
"value": "spectrum--medium"
},
{
"label": "Large",
"value": "spectrum--large"
}
]
}
]
},
@ -1102,5 +1123,34 @@
"defaultValue": true
}
]
},
"input": {
"name": "Text Field",
"description": "A textfield component that allows the user to input text.",
"icon": "ri-edit-box-line",
"styleable": true,
"bindable": true,
"settings": [
{
"type": "text",
"label": "Field",
"key": "field"
},
{
"type": "text",
"label": "Label",
"key": "label"
},
{
"type": "text",
"label": "Placeholder",
"key": "placeholder"
},
{
"type": "boolean",
"label": "Required",
"key": "required"
}
]
}
}

View File

@ -22,6 +22,7 @@
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-postcss": "^3.1.5",
"rollup-plugin-svelte": "^6.1.1",
"rollup-plugin-svg": "^2.0.0",
"rollup-plugin-terser": "^7.0.2",
"sirv-cli": "^0.4.4",
"svelte": "^3.30.0"
@ -33,10 +34,17 @@
"license": "MIT",
"gitHead": "62ebf3cedcd7e9b2494b4f8cbcfb90927609b491",
"dependencies": {
"@adobe/spectrum-css-workflow-icons": "^1.1.0",
"@budibase/bbui": "^1.52.4",
"@budibase/svelte-ag-grid": "^0.0.16",
"@spectrum-css/button": "^3.0.0-beta.6",
"@spectrum-css/icon": "^3.0.0-beta.2",
"@spectrum-css/page": "^3.0.0-beta.0",
"@spectrum-css/textfield": "^3.0.0-beta.6",
"@spectrum-css/vars": "^3.0.0-beta.2",
"apexcharts": "^3.22.1",
"flatpickr": "^4.6.6",
"loadicons": "^1.0.0",
"lodash.debounce": "^4.0.8",
"markdown-it": "^12.0.2",
"quill": "^1.3.7",

View File

@ -4,6 +4,7 @@ import svelte from "rollup-plugin-svelte"
import postcss from "rollup-plugin-postcss"
import json from "@rollup/plugin-json"
import { terser } from "rollup-plugin-terser"
import svg from "rollup-plugin-svg"
import builtins from "rollup-plugin-node-builtins"
@ -33,5 +34,6 @@ export default {
}),
commonjs(),
json(),
svg(),
],
}

View File

@ -1,5 +0,0 @@
<script>
import Form from "./Form.svelte"
</script>
<Form wide={false} />

View File

@ -1,5 +0,0 @@
<script>
import Form from "./Form.svelte"
</script>
<Form wide />

View File

@ -0,0 +1,123 @@
<script>
import { setContext, getContext, onMount } from "svelte"
import { writable, get } from "svelte/store"
export let datasource
export let theme
export let size
const { styleable, API } = getContext("sdk")
const component = getContext("component")
let loaded = false
let schema = {}
let fieldMap = {}
// Keep form state up to date with form fields
$: updateFormState(fieldMap)
// Form state contains observable data about the form
const formState = writable({ values: {}, errors: {}, valid: true })
// Form API contains functions to control the form
const formApi = {
registerField: (field, validate) => {
if (!field) {
return
}
if (fieldMap[field] != null) {
return fieldMap[field]
}
fieldMap[field] = {
fieldState: makeFieldState(field),
fieldApi: makeFieldApi(field, validate),
}
fieldMap = fieldMap
return fieldMap[field]
},
}
// Provide both form API and state to children
setContext("form", { formApi, formState })
// Creates an API for a specific field
const makeFieldApi = (field, validate) => {
return {
setValue: value => {
const { fieldState } = fieldMap[field]
fieldState.update(state => {
state.value = value
state.error = validate ? validate(value) : null
state.valid = !state.error
return state
})
fieldMap = fieldMap
},
}
}
// Creates observable state data about a specific field
const makeFieldState = field => {
return writable({
field,
value: null,
error: null,
valid: true,
})
}
// Updates the form states from the field data
const updateFormState = fieldMap => {
let values = {}
let errors = {}
Object.entries(fieldMap).forEach(([field, formField]) => {
const fieldState = get(formField.fieldState)
values[field] = fieldState.value
if (fieldState.error) {
errors[field] = fieldState.error
}
})
const valid = Object.keys(errors).length === 0
formState.set({ values, errors, valid })
}
// Fetches the form schema from this form's datasource, if one exists
const fetchSchema = async () => {
if (!datasource?.tableId) {
schema = {}
} else {
const table = await API.fetchTableDefinition(datasource?.tableId)
if (table) {
if (datasource.type === "query") {
schema = table.parameters
} else {
schema = table.schema || {}
}
console.log(table)
}
}
loaded = true
}
// Load the form schema on mount
onMount(fetchSchema)
</script>
<div
lang="en"
dir="ltr"
use:styleable={$component.styles}
class={`spectrum ${size || 'spectrum--medium'} ${theme || 'spectrum--light'}`}>
{#if loaded}
<slot />
{/if}
</div>
<style>
.spectrum :global(label) {
font-size: var(
--spectrum-alias-item-text-size-m,
var(--spectrum-global-dimension-font-size-100)
) !important;
}
</style>

View File

@ -0,0 +1,67 @@
<script>
import "@spectrum-css/textfield/dist/index-vars.css"
import { Label } from "@budibase/bbui"
import { getContext } from "svelte"
export let field
export let label
export let placeholder
export let validate = value => (value ? null : "Required")
const { styleable } = getContext("sdk")
const component = getContext("component")
const { formApi } = getContext("form") ?? {}
// Register this field with its form
const formField = formApi?.registerField(field, validate) ?? {}
const { fieldApi, fieldState } = formField
// Update value on blur only
const onBlur = event => {
fieldApi.setValue(event.target.value)
}
</script>
{#if !field}
<div>Add the Field setting to start using your component!</div>
{:else if !fieldState}
<div>Form components need to be wrapped in a Form.</div>
{:else}
<div class="container" use:styleable={$component.styles}>
{#if label}
<Label grey>{label}</Label>
{/if}
<div class="spectrum-Textfield" class:is-invalid={!$fieldState.valid}>
{#if !$fieldState.valid}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<input
value={$fieldState.value || ''}
placeholder={placeholder || ''}
on:blur={onBlur}
type="text"
class="spectrum-Textfield-input" />
</div>
{#if $fieldState.error}
<div class="error">
<Label>{$fieldState.error}</Label>
</div>
{/if}
</div>
{/if}
<style>
.error :global(label) {
color: var(
--spectrum-semantic-negative-color-default,
var(--spectrum-global-color-red-500)
) !important;
margin-top: var(--spacing-s) !important;
margin-bottom: 0 !important;
}
</style>

View File

@ -0,0 +1,2 @@
export { default as form } from "./Form.svelte"
export { default as input } from "./Input.svelte"

View File

@ -1,17 +1,25 @@
import "@budibase/bbui/dist/bbui.css"
import "flatpickr/dist/flatpickr.css"
import "@spectrum-css/vars/dist/spectrum-global.css"
import "@spectrum-css/vars/dist/spectrum-medium.css"
import "@spectrum-css/vars/dist/spectrum-large.css"
import "@spectrum-css/vars/dist/spectrum-light.css"
import "@spectrum-css/vars/dist/spectrum-dark.css"
import "@spectrum-css/vars/dist/spectrum-darkest.css"
import "@spectrum-css/page/dist/index-vars.css"
import "@spectrum-css/button/dist/index-vars.css"
import { loadSpectrumIcons } from "./spectrum-icons"
loadSpectrumIcons()
export { default as container } from "./Container.svelte"
export { default as datagrid } from "./grid/Component.svelte"
export { default as screenslot } from "./ScreenSlot.svelte"
export { default as button } from "./Button.svelte"
export { default as input } from "./Input.svelte"
export { default as richtext } from "./RichText.svelte"
export { default as list } from "./List.svelte"
export { default as stackedlist } from "./StackedList.svelte"
export { default as card } from "./Card.svelte"
export { default as dataform } from "./DataForm.svelte"
export { default as dataformwide } from "./DataFormWide.svelte"
export { default as datepicker } from "./DatePicker.svelte"
export { default as text } from "./Text.svelte"
export { default as login } from "./Login.svelte"
@ -26,3 +34,4 @@ export { default as cardstat } from "./CardStat.svelte"
export { default as newrow } from "./NewRow.svelte"
export { default as icon } from "./Icon.svelte"
export * from "./charts"
export * from "./forms"

View File

@ -0,0 +1,32 @@
import "@spectrum-css/icon/dist/index-vars.css"
import SpectrumUIIcons from "@spectrum-css/icon/dist/spectrum-css-icons.svg"
import SpectrumWorkflowIcons from "@adobe/spectrum-css-workflow-icons/dist/spectrum-icons.svg"
export const loadSpectrumIcons = () => {
loadIconSet("Spectrum UI Icons", SpectrumUIIcons)
loadIconSet("Spectrum Workflow Icons", SpectrumWorkflowIcons)
}
const loadIconSet = (name, markup) => {
// Parse the SVG
const parser = new DOMParser()
try {
const doc = parser.parseFromString(markup, "image/svg+xml")
const svg = doc.firstChild
// Check a real SVG was parsed
if (svg && svg.tagName === "svg") {
// Hide the element
svg.style.display = "none"
// Insert it into the head
document.head.insertBefore(svg, null)
} else {
throw "Invalid tag type for SVG definition"
}
} catch (err) {
// Swallow error, but icons won't work
console.error(err)
console.error(`Failed to parse ${name}. Icons won't work.`)
}
}

View File

@ -2,6 +2,11 @@
# yarn lockfile v1
"@adobe/spectrum-css-workflow-icons@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@adobe/spectrum-css-workflow-icons/-/spectrum-css-workflow-icons-1.1.0.tgz#79e97f86130e1a30b84c8524cebff93a600dbb8a"
integrity sha512-07ec4Pfr+W5II4a36nto3jShBZTbpe39lB977ULYN436UTQsnAdYupezBwFd7eEvOMUawgKfqnxyR2oPxp1SMQ==
"@babel/code-frame@^7.10.4":
version "7.10.4"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
@ -127,6 +132,33 @@
estree-walker "^1.0.1"
picomatch "^2.2.2"
"@spectrum-css/button@^3.0.0-beta.6":
version "3.0.0-beta.6"
resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.0-beta.6.tgz#007919d3e7a6692e506dc9addcd46aee6b203b1a"
integrity sha512-ZoJxezt5Pc006RR7SMG7PfC0VAdWqaGDpd21N8SEykGuz/KmNulqGW8RiSZQGMVX/jk5ZCAthPrH8cI/qtKbMg==
"@spectrum-css/icon@^3.0.0-beta.2":
version "3.0.0-beta.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.0-beta.2.tgz#2dd7258ded74501b56e5fc42d0b6f0a3f4936aeb"
integrity sha512-BEHJ68YIXSwsNAqTdq/FrS4A+jtbKzqYrsGKXdDf93ql+fHWYXRCh1EVYGHx/1696mY73DhM4snMpKGIFtXGFA==
"@spectrum-css/page@^3.0.0-beta.0":
version "3.0.0-beta.0"
resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.0-beta.0.tgz#885ea41b44861c5dc3aac904536f9e93c9109b58"
integrity sha512-+OD+l3aLisykxJnHfLkdkxMS1Uj1vKGYpKil7W0r5lSWU44eHyRgb8ZK5Vri1+sUO5SSf/CTybeVwtXME9wMLA==
dependencies:
"@spectrum-css/vars" "^3.0.0-beta.2"
"@spectrum-css/textfield@^3.0.0-beta.6":
version "3.0.0-beta.6"
resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.0-beta.6.tgz#30c044ceb403d6ea82d8046fb8f767f7fe455da6"
integrity sha512-U7P8C3Xx8h5X+r+dZu1qbxceIxBn7ZSmMvJyC7MPSPcU3EwdzCUepERNGX7NrQdcX91XSNlPUOF7hZUognBwhQ==
"@spectrum-css/vars@^3.0.0-beta.2":
version "3.0.0-beta.2"
resolved "https://registry.yarnpkg.com/@spectrum-css/vars/-/vars-3.0.0-beta.2.tgz#f0b3a2db44aa57b1a82e47ab392c716a3056a157"
integrity sha512-HpcRDUkSjKVWUi7+jf6zp33YszXs3qFljaaNVTVOf0m0mqjWWXHxgLrvYlFFlHp5ITbNXds5Cb7EgiXCKmVIpA==
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
@ -979,6 +1011,11 @@ esprima@^4.0.0:
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
estree-walker@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.2.1.tgz#bdafe8095383d8414d5dc2ecf4c9173b6db9412e"
integrity sha1-va/oCVOD2EFNXcLs9MkXO225QS4=
estree-walker@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-0.6.1.tgz#53049143f40c6eb918b23671d1fe3219f3a1b362"
@ -1557,6 +1594,11 @@ loader-utils@^1.1.0:
emojis-list "^3.0.0"
json5 "^1.0.1"
loadicons@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/loadicons/-/loadicons-1.0.0.tgz#79fd9b08ef2933988c94068cbd246ef3f21cbd04"
integrity sha512-KSywiudfuOK5sTdhNMM8hwRpMxZ5TbQlU4ZijMxUFwRW7jpxUmb9YJoLIzDn7+xuxeLzCZWBmLJS2JDjDWCpsw==
local-access@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/local-access/-/local-access-1.0.1.tgz#5121258146d64e869046c642ea4f1dd39ff942bb"
@ -1662,7 +1704,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1:
resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a"
integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=
minimatch@^3.0.4:
minimatch@^3.0.2, minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
@ -2456,6 +2498,13 @@ rollup-plugin-svelte@^6.1.1:
rollup-pluginutils "^2.8.2"
sourcemap-codec "^1.4.8"
rollup-plugin-svg@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/rollup-plugin-svg/-/rollup-plugin-svg-2.0.0.tgz#ce11b55e915d5b2190328c4e6632bd6b4fe12ee9"
integrity sha512-DmE7dSQHo1SC5L2uH2qul3Mjyd5oV6U1aVVkyvTLX/mUsRink7f1b1zaIm+32GEBA6EHu8H/JJi3DdWqM53ySQ==
dependencies:
rollup-pluginutils "^1.3.1"
rollup-plugin-terser@^7.0.2:
version "7.0.2"
resolved "https://registry.yarnpkg.com/rollup-plugin-terser/-/rollup-plugin-terser-7.0.2.tgz#e8fbba4869981b2dc35ae7e8a502d5c6c04d324d"
@ -2466,6 +2515,14 @@ rollup-plugin-terser@^7.0.2:
serialize-javascript "^4.0.0"
terser "^5.0.0"
rollup-pluginutils@^1.3.1:
version "1.5.2"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-1.5.2.tgz#1e156e778f94b7255bfa1b3d0178be8f5c552408"
integrity sha1-HhVud4+UtyVb+hs9AXi+j1xVJAg=
dependencies:
estree-walker "^0.2.1"
minimatch "^3.0.2"
rollup-pluginutils@^2.8.2:
version "2.8.2"
resolved "https://registry.yarnpkg.com/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz#72f2af0748b592364dbd3389e600e5a9444a351e"