Add common SpectrumField component, add spectrum labels, add form label position customisation

This commit is contained in:
Andrew Kingston 2021-01-27 10:59:05 +00:00
parent 365c503224
commit 12f3b7f6fa
9 changed files with 311 additions and 91 deletions

View File

@ -74,6 +74,7 @@ export const styleable = (node, styles = {}) => {
// preview // preview
const applyStyles = styleString => { const applyStyles = styleString => {
node.style = addBuilderPreviewStyles(styleString, componentId, selectable) node.style = addBuilderPreviewStyles(styleString, componentId, selectable)
node.dataset.componentId = componentId
} }
// Applies the "normal" style definition // Applies the "normal" style definition

View File

@ -114,57 +114,6 @@
} }
] ]
}, },
"form": {
"name": "Form",
"icon": "ri-file-edit-line",
"styleable": true,
"hasChildren": true,
"dataProvider": true,
"datasourceSetting": "datasource",
"settings": [
{
"type": "datasource",
"label": "Data",
"key": "datasource"
},
{
"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"
}
]
}
]
},
"richtext": { "richtext": {
"name": "Rich Text", "name": "Rich Text",
"description": "A component that allows the user to enter long form text.", "description": "A component that allows the user to enter long form text.",
@ -1121,6 +1070,77 @@
} }
] ]
}, },
"form": {
"name": "Form",
"icon": "ri-file-edit-line",
"styleable": true,
"hasChildren": true,
"dataProvider": true,
"datasourceSetting": "datasource",
"settings": [
{
"type": "datasource",
"label": "Data",
"key": "datasource"
},
{
"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"
}
]
},
{
"type": "select",
"label": "Labels",
"key": "labelPosition",
"defaultValue": "left",
"options": [
{
"label": "Left",
"value": "left"
},
{
"label": "Right",
"value": "right"
},
{
"label": "Above",
"value": "above"
}
]
}
]
},
"stringfield": { "stringfield": {
"name": "Text Field", "name": "Text Field",
"description": "A textfield component that allows the user to input text.", "description": "A textfield component that allows the user to input text.",
@ -1189,7 +1209,8 @@
{ {
"type": "text", "type": "text",
"label": "Placeholder", "label": "Placeholder",
"key": "placeholder" "key": "placeholder",
"placeholder": "Choose an option"
} }
] ]
} }

View File

@ -37,9 +37,15 @@
"@adobe/spectrum-css-workflow-icons": "^1.1.0", "@adobe/spectrum-css-workflow-icons": "^1.1.0",
"@budibase/bbui": "^1.52.4", "@budibase/bbui": "^1.52.4",
"@budibase/svelte-ag-grid": "^0.0.16", "@budibase/svelte-ag-grid": "^0.0.16",
"@spectrum-css/actionbutton": "^1.0.0-beta.1",
"@spectrum-css/button": "^3.0.0-beta.6", "@spectrum-css/button": "^3.0.0-beta.6",
"@spectrum-css/fieldlabel": "^3.0.0-beta.7",
"@spectrum-css/icon": "^3.0.0-beta.2", "@spectrum-css/icon": "^3.0.0-beta.2",
"@spectrum-css/menu": "^3.0.0-beta.5",
"@spectrum-css/page": "^3.0.0-beta.0", "@spectrum-css/page": "^3.0.0-beta.0",
"@spectrum-css/picker": "^1.0.0-beta.3",
"@spectrum-css/popover": "^3.0.0-beta.6",
"@spectrum-css/stepper": "^3.0.0-beta.7",
"@spectrum-css/textfield": "^3.0.0-beta.6", "@spectrum-css/textfield": "^3.0.0-beta.6",
"@spectrum-css/vars": "^3.0.0-beta.2", "@spectrum-css/vars": "^3.0.0-beta.2",
"apexcharts": "^3.22.1", "apexcharts": "^3.22.1",

View File

@ -1,4 +1,5 @@
<script> <script>
import "@spectrum-css/fieldlabel/dist/index-vars.css"
import { setContext, getContext, onMount } from "svelte" import { setContext, getContext, onMount } from "svelte"
import { writable, get } from "svelte/store" import { writable, get } from "svelte/store"
import { createValidatorFromConstraints } from "./validation" import { createValidatorFromConstraints } from "./validation"
@ -6,6 +7,7 @@
export let datasource export let datasource
export let theme export let theme
export let size export let size
export let labelPosition = "left"
const { styleable, API } = getContext("sdk") const { styleable, API } = getContext("sdk")
const component = getContext("component") const component = getContext("component")
@ -38,6 +40,10 @@
fieldMap[field] = { fieldMap[field] = {
fieldState: makeFieldState(field), fieldState: makeFieldState(field),
fieldApi: makeFieldApi(field, validate), fieldApi: makeFieldApi(field, validate),
fieldSchema: schema?.[field] ?? {},
fieldId: `${Math.random()
.toString(32)
.substr(2)}/${field}`,
} }
fieldMap = fieldMap fieldMap = fieldMap
return fieldMap[field] return fieldMap[field]
@ -45,7 +51,7 @@
} }
// Provide both form API and state to children // Provide both form API and state to children
setContext("form", { formApi, formState }) setContext("form", { formApi, formState, labelPosition })
// Creates an API for a specific field // Creates an API for a specific field
const makeFieldApi = (field, validate) => { const makeFieldApi = (field, validate) => {
@ -116,16 +122,11 @@
dir="ltr" dir="ltr"
use:styleable={$component.styles} use:styleable={$component.styles}
class={`spectrum ${size || 'spectrum--medium'} ${theme || 'spectrum--light'}`}> class={`spectrum ${size || 'spectrum--medium'} ${theme || 'spectrum--light'}`}>
<form
class="spectrum-Form"
class:spectrum-Form--labelsAbove={labelPosition === 'above'}>
{#if loaded} {#if loaded}
<slot /> <slot />
{/if} {/if}
</form>
</div> </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

@ -1 +1,107 @@
Select <script>
import "@spectrum-css/picker/dist/index-vars.css"
import "@spectrum-css/popover/dist/index-vars.css"
import "@spectrum-css/menu/dist/index-vars.css"
import { getContext } from "svelte"
import SpectrumField from "./SpectrumField.svelte"
export let field
export let label
export let placeholder
// Register this field with its form
const { formApi } = getContext("form") ?? {}
const formField = formApi?.registerField(field) ?? {}
const { fieldApi, fieldState, fieldSchema } = formField
// Picker state
let open = false
$: options = fieldSchema?.constraints?.inclusion ?? []
$: placeholderText = placeholder || "Choose an option"
$: isNull = $fieldState.value == null || $fieldState.value === ""
// Update value on blur only
const selectOption = value => {
fieldApi.setValue(value)
open = false
}
</script>
<SpectrumField {field} {label}>
<button
id={$fieldState.fieldId}
class="spectrum-Picker"
class:is-invalid={!$fieldState.valid}
class:is-open={open}
aria-haspopup="listbox"
on:click={() => (open = true)}>
<span class="spectrum-Picker-label" class:is-placeholder={isNull}>
{isNull ? placeholderText : $fieldState.value}
</span>
{#if !$fieldState.valid}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
focusable="false"
aria-hidden="true"
aria-label="Folder">
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<svg
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Chevron100" />
</svg>
</button>
<div
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover"
class:is-open={open}>
<ul class="spectrum-Menu" role="listbox">
<li
class="spectrum-Menu-item"
class:is-selected={isNull}
role="option"
aria-selected="true"
tabindex="0"
on:click={() => selectOption(null)}>
<span class="spectrum-Menu-itemLabel">{placeholderText}</span>
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
</li>
{#each options as option}
<li
class="spectrum-Menu-item"
class:is-selected={option === $fieldState.value}
role="option"
aria-selected="true"
tabindex="0"
on:click={() => selectOption(option)}>
<span class="spectrum-Menu-itemLabel">{option}</span>
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
</li>
{/each}
</ul>
</div>
{#if $fieldState.error}
<div class="error">{$fieldState.error}</div>
{/if}
</SpectrumField>
<style>
.error {
color: var(
--spectrum-semantic-negative-color-default,
var(--spectrum-global-color-red-500)
) !important;
}
</style>

View File

@ -0,0 +1,36 @@
<script>
import Placeholder from "./Placeholder.svelte"
import { getContext } from "svelte"
export let label
export let field
const formContext = getContext("form")
const { styleable } = getContext("sdk")
const component = getContext("component")
const { labelPosition, formApi } = formContext || {}
const formField = formApi?.registerField(field) ?? {}
const { fieldId } = formField
$: labelPositionClass =
labelPosition === "top" ? "" : `spectrum-FieldLabel--${labelPosition}`
</script>
{#if !fieldId}
<Placeholder>Add the Field setting to start using your component</Placeholder>
{:else if !formContext}
<Placeholder>Form components need to be wrapped in a Form</Placeholder>
{:else}
<div class="spectrum-Form-item" use:styleable={$component.styles}>
{#if label}
<label
for={fieldId}
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${labelPositionClass}`}>
{label}
</label>
{/if}
<div class="spectrum-Form-itemField">
<slot />
</div>
</div>
{/if}

View File

@ -1,38 +1,34 @@
<script> <script>
import "@spectrum-css/textfield/dist/index-vars.css" import "@spectrum-css/textfield/dist/index-vars.css"
import { Label } from "@budibase/bbui" import "@spectrum-css/actionbutton/dist/index-vars.css"
import "@spectrum-css/stepper/dist/index-vars.css"
import { getContext } from "svelte" import { getContext } from "svelte"
import Placeholder from "./Placeholder.svelte" import SpectrumField from "./SpectrumField.svelte"
export let field export let field
export let label export let label
export let placeholder export let placeholder
export let type = "text" export let type = "text"
const { styleable } = getContext("sdk")
const component = getContext("component")
const { formApi } = getContext("form") ?? {}
// Register this field with its form // Register this field with its form
const formField = formApi?.registerField(field, validate) ?? {} const { formApi } = getContext("form") ?? {}
const formField = formApi?.registerField(field) ?? {}
const { fieldApi, fieldState } = formField const { fieldApi, fieldState } = formField
$: numeric = type === "number"
// Update value on blur only // Update value on blur only
const onBlur = event => { const onBlur = event => {
fieldApi.setValue(event.target.value) fieldApi.setValue(event.target.value)
} }
</script> </script>
{#if !field} <SpectrumField {label} {field}>
<Placeholder>Add the Field setting to start using your component</Placeholder> <div class:spectrum-Stepper={type === 'number'}>
{:else if !fieldState} <div
<Placeholder>Form components need to be wrapped in a Form</Placeholder> class="spectrum-Textfield"
{:else} class:spectrum-Stepper-textfield={numeric}
<div class="container" use:styleable={$component.styles}> class:is-invalid={!$fieldState.valid}>
{#if label}
<Label grey>{label}</Label>
{/if}
<div class="spectrum-Textfield" class:is-invalid={!$fieldState.valid}>
{#if !$fieldState.valid} {#if !$fieldState.valid}
<svg <svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon" class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
@ -42,27 +38,49 @@
</svg> </svg>
{/if} {/if}
<input <input
id={$fieldState.fieldId}
value={$fieldState.value || ''} value={$fieldState.value || ''}
placeholder={placeholder || ''} placeholder={placeholder || ''}
on:blur={onBlur} on:blur={onBlur}
{type} {type}
class="spectrum-Textfield-input" /> class="spectrum-Textfield-input"
class:spectrum-Stepper-input={numeric} />
</div> </div>
{#if numeric}
<span class="spectrum-Stepper-buttons">
<button
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-Stepper-stepUp"
tabindex="-1">
<svg
class="spectrum-Icon spectrum-UIIcon-ChevronUp75 spectrum-Stepper-stepUpIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Chevron75" />
</svg>
</button>
<button
class="spectrum-ActionButton spectrum-ActionButton--sizeM spectrum-Stepper-stepDown"
tabindex="-1">
<svg
class="spectrum-Icon spectrum-UIIcon-ChevronDown75 spectrum-Stepper-stepDownIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Chevron75" />
</svg>
</button>
</span>
{/if}
{#if $fieldState.error} {#if $fieldState.error}
<div class="error"> <div class="error">{$fieldState.error}</div>
<Label>{$fieldState.error}</Label>
</div>
{/if} {/if}
</div> </div>
{/if} </SpectrumField>
<style> <style>
.error :global(label) { .error {
color: var( color: var(
--spectrum-semantic-negative-color-default, --spectrum-semantic-negative-color-default,
var(--spectrum-global-color-red-500) var(--spectrum-global-color-red-500)
) !important; ) !important;
margin-top: var(--spacing-s) !important;
margin-bottom: 0 !important;
} }
</style> </style>

View File

@ -132,16 +132,31 @@
estree-walker "^1.0.1" estree-walker "^1.0.1"
picomatch "^2.2.2" picomatch "^2.2.2"
"@spectrum-css/actionbutton@^1.0.0-beta.1":
version "1.0.0-beta.1"
resolved "https://registry.yarnpkg.com/@spectrum-css/actionbutton/-/actionbutton-1.0.0-beta.1.tgz#a6684cac108d4a9daefe0be6df8201d3c369a0d6"
integrity sha512-QbrPMTkbkmh+dEBP66TFXmF5z3qSde+BnLR5hnlo2XMvKvnblX2VJStEbQ+hTKuSZXCRFADXyXD5o0NOYDTByQ==
"@spectrum-css/button@^3.0.0-beta.6": "@spectrum-css/button@^3.0.0-beta.6":
version "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" resolved "https://registry.yarnpkg.com/@spectrum-css/button/-/button-3.0.0-beta.6.tgz#007919d3e7a6692e506dc9addcd46aee6b203b1a"
integrity sha512-ZoJxezt5Pc006RR7SMG7PfC0VAdWqaGDpd21N8SEykGuz/KmNulqGW8RiSZQGMVX/jk5ZCAthPrH8cI/qtKbMg== integrity sha512-ZoJxezt5Pc006RR7SMG7PfC0VAdWqaGDpd21N8SEykGuz/KmNulqGW8RiSZQGMVX/jk5ZCAthPrH8cI/qtKbMg==
"@spectrum-css/fieldlabel@^3.0.0-beta.7":
version "3.0.0-beta.7"
resolved "https://registry.yarnpkg.com/@spectrum-css/fieldlabel/-/fieldlabel-3.0.0-beta.7.tgz#f37797565e21b3609b8fbc2dafcea8ea41ffa114"
integrity sha512-0pseiPghqlOdALsRtidveWyt2YjfSXTZWDlSkcne/J0/QXBJOQH/7Qfy7TmROQZYRB2LqH1VzmE1zbvGwr5Aog==
"@spectrum-css/icon@^3.0.0-beta.2": "@spectrum-css/icon@^3.0.0-beta.2":
version "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" resolved "https://registry.yarnpkg.com/@spectrum-css/icon/-/icon-3.0.0-beta.2.tgz#2dd7258ded74501b56e5fc42d0b6f0a3f4936aeb"
integrity sha512-BEHJ68YIXSwsNAqTdq/FrS4A+jtbKzqYrsGKXdDf93ql+fHWYXRCh1EVYGHx/1696mY73DhM4snMpKGIFtXGFA== integrity sha512-BEHJ68YIXSwsNAqTdq/FrS4A+jtbKzqYrsGKXdDf93ql+fHWYXRCh1EVYGHx/1696mY73DhM4snMpKGIFtXGFA==
"@spectrum-css/menu@^3.0.0-beta.5":
version "3.0.0-beta.5"
resolved "https://registry.yarnpkg.com/@spectrum-css/menu/-/menu-3.0.0-beta.5.tgz#99d5ea7f6760b7a89d5d732f4e91b98dd3f82d74"
integrity sha512-jvPD5GbNdX31rdFBLxCG7KoUVGeeNYLzNXDpiGZsWme/djVTwitljgNe7bhVwCVlXZE7H20Ti/YrdafnE154Rw==
"@spectrum-css/page@^3.0.0-beta.0": "@spectrum-css/page@^3.0.0-beta.0":
version "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" resolved "https://registry.yarnpkg.com/@spectrum-css/page/-/page-3.0.0-beta.0.tgz#885ea41b44861c5dc3aac904536f9e93c9109b58"
@ -149,6 +164,21 @@
dependencies: dependencies:
"@spectrum-css/vars" "^3.0.0-beta.2" "@spectrum-css/vars" "^3.0.0-beta.2"
"@spectrum-css/picker@^1.0.0-beta.3":
version "1.0.0-beta.3"
resolved "https://registry.yarnpkg.com/@spectrum-css/picker/-/picker-1.0.0-beta.3.tgz#476593597b5a9e0105397e4e39350869cf6e7965"
integrity sha512-jHzFnS5Frd3JSwZ6B8ymH/sVnNqAUBo9p93Zax4VHTUDsPTtTkvxj/Vxo4POmrJEL9v3qUB2Yk13rD2BSfEzLQ==
"@spectrum-css/popover@^3.0.0-beta.6":
version "3.0.0-beta.6"
resolved "https://registry.yarnpkg.com/@spectrum-css/popover/-/popover-3.0.0-beta.6.tgz#787611f020e091234e6ba7e946b0dbd0ed1a2fa2"
integrity sha512-dUJlwxoNpB6jOR0g/ywH2cPoUz2FVsL6xPfkm6BSsLp9ejhYy0/OFF4w0Q32Fu9qJDbWJ9qaoOlPpt7IjQ+/GQ==
"@spectrum-css/stepper@^3.0.0-beta.7":
version "3.0.0-beta.7"
resolved "https://registry.yarnpkg.com/@spectrum-css/stepper/-/stepper-3.0.0-beta.7.tgz#fc78435ce878c5e233af13e43ed2c3e8671a2bbc"
integrity sha512-TQL2OBcdEgbHBwehMGgqMuWdKZZQPGcBRV5FlF0TUdOT58lEqFAO43Gajqvyte1P23lNmnX8KuMwkRfQdn0RzA==
"@spectrum-css/textfield@^3.0.0-beta.6": "@spectrum-css/textfield@^3.0.0-beta.6":
version "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" resolved "https://registry.yarnpkg.com/@spectrum-css/textfield/-/textfield-3.0.0-beta.6.tgz#30c044ceb403d6ea82d8046fb8f767f7fe455da6"