Completed checkbox and form field. Changes to test app (#91)

* Completed checkbox and form field component with changes around how test app renders components

* Tidyup
This commit is contained in:
Conor_Mack 2020-02-12 12:32:46 +00:00 committed by GitHub
parent 34b957f331
commit 74bc97f3d4
13 changed files with 243 additions and 60 deletions

View File

@ -40,6 +40,8 @@
"license": "MIT", "license": "MIT",
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072", "gitHead": "115189f72a850bfb52b65ec61d932531bf327072",
"dependencies": { "dependencies": {
"@material/checkbox": "^4.0.0",
"@material/form-field": "^4.0.0",
"@material/textfield": "^4.0.0" "@material/textfield": "^4.0.0"
} }
} }

View File

@ -0,0 +1,49 @@
<script>
import { onMount, onDestroy, getContext } from "svelte";
import Formfield from "../Common/Formfield.svelte";
import { fieldStore } from "../Common/FormfieldStore.js";
import ClassBuilder from "../ClassBuilder.js";
import { MDCCheckbox } from "@material/checkbox";
export let id = "";
export let label = "";
export let disabled = false;
export let alignEnd = false;
export let indeterminate = false;
let instance = null;
let checkbox = null;
onMount(() => {
if (!!checkbox) {
instance = new MDCCheckbox(checkbox);
let fieldStore = getContext("BBMD:field-element");
fieldStore.setInput(instance);
instance.indeterminate = indeterminate;
}
});
const cb = new ClassBuilder("checkbox");
let modifiers = { disabled };
let props = { modifiers };
const blockClass = cb.build({ props });
</script>
<!-- TODO: Customizing Colour and Density - What level of customization for these things does Budibase need here? -->
<Formfield {label} {id} {alignEnd}>
<div bind:this={checkbox} class={blockClass}>
<input type="checkbox" class={cb.elem`native-control`} {id} {disabled} />
<div class={cb.elem`background`}>
<svg class={cb.elem`checkmark`} viewBox="0 0 24 24">
<path
class={cb.elem`checkmark-path`}
fill="none"
d="M1.73,12.91 8.1,19.28 22.79,4.59" />
</svg>
<div class={cb.elem`mixedmark`} />
</div>
<div class={cb.elem`ripple`} />
</div>
</Formfield>

View File

@ -0,0 +1,2 @@
@import "@material/form-field/mdc-form-field";
@import "@material/checkbox/mdc-checkbox.scss";

View File

@ -0,0 +1,2 @@
import "./_style.scss";
export { default as checkbox } from "./Checkbox.svelte";

View File

@ -1,38 +1,74 @@
export default class ClassBuilder { export default class ClassBuilder {
constructor(block, customDefaults) { constructor(block, defaultIgnoreList) {
this.block = `mdc-${block}` this.block = `mdc-${block}`;
this.customDefaults = customDefaults //will be ignored when building custom classes this.defaultIgnoreList = defaultIgnoreList; //will be ignored when building custom classes
} }
// classParams: {modifiers:[] (mdc), custom:[] (bbmd), extra:[] (any)} /*
blocks(classParams) { handles both blocks and elementss (BEM MD Notation)
let base = this.block params = {elementName: string, props: {modifiers{}, customs:{}, extras: []}}
if (classParams == undefined) return base All are optional
return this.buildClass(base, classParams) */
build(params) {
if (!params) return this.block; //return block if nothing passed
const { props, elementName } = params;
let base = !!elementName ? `${this.block}__${elementName}` : this.block;
if (!props) return base;
return this._handleProps(base, props);
} }
//elementName: string, classParams: {} //Easily grab a simple element class
elements(elementName, classParams) { elem(elementName) {
let base = `${this.block}__${elementName}` return this.build({ elementName });
if (classParams == undefined) return base
return this.buildClass(base, classParams)
} }
buildClass(base, classParams) { //use if a different base is needed than whats defined by this.block
let cls = base debase(base, elementProps) {
const { modifiers, customs, extras } = classParams if (!elementProps) return base;
if (modifiers) cls += modifiers.map(m => ` ${base}--${m}`).join(" ") return this._handleProps(base, elementProps);
if (customs) }
cls += Object.entries(customs)
.map(([property, value]) => { //proxies bindProps and checks for which elementProps exist before binding
//disregard falsy and values set by customDefaults constructor param _handleProps(base, elementProps) {
if (!!value && !this.customDefaults.includes(value)) { let cls = base;
//custom scss name convention = bbmd-[block | element]--[property]-[value] const { modifiers, customs, extras } = elementProps;
return ` bbmd-${base}--${property}-${value}` if (!!modifiers) cls += this._bindProps(modifiers, base);
if (!!customs) cls += this._bindProps(customs, base, true);
if (!!extras) cls += ` ${extras.join(" ")}`;
return cls.trim();
}
/*
Handles both modifiers and customs. Use property, value or both depending
on whether it is passsed props for custom or modifiers
if custom uses the following convention for scss mixins:
bbmd-{this.block}--{property}-{value}
bbmd-mdc-button--size-large
*/
_bindProps(elementProps, base, isCustom = false) {
return Object.entries(elementProps)
.map(([property, value]) => {
//disregard falsy and values set by defaultIgnoreList constructor param
if (
!!value &&
(!this.defaultIgnoreList || !this.defaultIgnoreList.includes(value))
) {
let classBase = isCustom ? `bbmd-${base}` : `${base}`;
let valueType = typeof value;
if (valueType == "string" || valueType == "number") {
return isCustom
? ` ${classBase}--${this._convertCamel(property)}-${value}`
: ` ${classBase}--${value}`;
} else if (valueType == "boolean") {
return ` ${classBase}--${this._convertCamel(property)}`;
} }
}) }
.join("") })
if (extras) cls += ` ${extras.join(" ")}` .join("");
return cls.trim() }
_convertCamel(str) {
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`);
} }
} }

View File

@ -0,0 +1,35 @@
<script>
import "@material/form-field/mdc-form-field.scss";
import ClassBuilder from "../ClassBuilder.js";
import { fieldStore } from "./FormfieldStore.js";
import { MDCFormField } from "@material/form-field";
import { onMount, onDestroy, setContext } from "svelte";
const cb = new ClassBuilder("form-field");
let store;
const unsubscribe = fieldStore.subscribe(s => (store = s));
export let id = "";
export let label = "";
export let alignEnd = false;
let formField = null;
let modifiers = { alignEnd };
let props = { modifiers };
let blockClasses = cb.build({ props });
onMount(() => {
if (!!formField) fieldStore.set(new MDCFormField(formField));
setContext("BBMD:field-element", fieldStore);
});
onDestroy(unsubscribe);
</script>
<div bind:this={formField} class={blockClasses}>
<slot />
<label for={id}>{label}</label>
</div>

View File

@ -0,0 +1,19 @@
import { writable } from "svelte/store";
function store() {
const { set, update, subscribe } = writable({});
function setInput(inp) {
update(n => {
n.input = inp;
});
}
return {
subscribe,
set,
setInput
};
}
export const fieldStore = store();

View File

@ -1,19 +1,22 @@
<script> <script>
import createApp from "./createApp" import createApp from "./createApp"
import { props } from "./props" import { props } from "./props"
let _bb let _bb
const { h1, overline, button, textfield, checkbox } = props
const _appPromise = createApp()
_appPromise.then(a => (_bb = a))
const { h1, overline, button, textfield } = props
let currentComponent let currentComponent
let _appPromise
$: { $: {
if (_bb && currentComponent) { if (currentComponent) {
_bb.hydrateChildren([h1, overline, button, textfield], currentComponent) const _appPromise = createApp()
const page = {
props: {
_component: "testcomponents/rootComponent",
_children: [h1, overline, button, textfield, checkbox],
},
}
_appPromise.then(initialise => {
initialise(page, window.document.body, "")
})
} }
} }
</script> </script>
@ -21,9 +24,7 @@
{#await _appPromise} {#await _appPromise}
loading loading
{:then _bb} {:then _bb}
<div id="current_component" bind:this={currentComponent} /> <div id="current_component" bind:this={currentComponent} />
{/await} {/await}
<style> <style>

View File

@ -1,17 +1,24 @@
import { createApp } from "@budibase/client/src/createApp" import { createApp } from "@budibase/client/src/createApp"
import components from "./testComponents" import components from "./testComponents"
import packageJson from "../../package.json" import packageJson from "../../package.json"
import { rootComponent } from "./rootComponent"
export default async () => { export default async props => {
delete components._lib delete components._lib
const componentLibraries = {} const componentLibraries = {}
componentLibraries[packageJson.name] = components componentLibraries[packageJson.name] = components
componentLibraries["testcomponents"] = {
rootComponent: rootComponent(window)
}
const appDef = { hierarchy: {}, actions: {} } const appDef = { hierarchy: {}, actions: {} }
const user = { name: "yeo", permissions: [] } const user = { name: "yeo", permissions: [] }
const { initialisePage } = createApp(
var app = createApp(window.document, componentLibraries, appDef, user, {}) window.document,
componentLibraries,
return app { appRootPath: "" },
appDef,
user,
{},
[]
)
return initialisePage
} }

View File

@ -1,5 +1,3 @@
const getComponent = comp => `@budibase/materialdesign-components/${comp}`;
export const props = { export const props = {
h1: { h1: {
@ -8,9 +6,9 @@ export const props = {
text: "Im a big header", text: "Im a big header",
}, },
overline: { overline: {
_component: getComponent`Overline`, _component: "@budibase/materialdesign-components/Overline",
_children: [], _children: [],
text: "A wee Overline", text: "Im a wee overline",
}, },
button: { button: {
_component: "@budibase/materialdesign-components/button", _component: "@budibase/materialdesign-components/button",
@ -36,7 +34,14 @@ export const props = {
label: "First", label: "First",
colour: "secondary", colour: "secondary",
textarea: true, textarea: true,
fullwidth:true, fullwidth: true,
helperText: "Add Surname", helperText: "Add Surname",
useCharCounter: true useCharCounter: true,
},
checkbox: {
_component: "@budibase/materialdesign-components/checkbox",
_children: [],
id: "test-check",
label: "Check Yo Self",
}
} }

View File

@ -0,0 +1,15 @@
export const rootComponent = window => {
return function(opts) {
const node = window.document.createElement("DIV")
const $set = props => {
props._bb.hydrateChildren(props._children, node)
}
const $destroy = () => {
if (opts.target && node) opts.target.removeChild(node)
}
this.$set = $set
this.$set(opts.props)
this.$destroy = $destroy
opts.target.appendChild(node)
}
}

View File

@ -1,4 +1,3 @@
import h1 from "../H1.svelte" import { H1, Overline, button, icon, checkbox, textfield } from "@BBMD"
import { button, icon } from "@BBMD"
export default { h1, button, icon } export default {H1, Overline, button, icon, checkbox, textfield }

View File

@ -1,3 +1,14 @@
export { default as h1 } from "./H1.svelte" import "@material/theme/mdc-theme.scss";
export { default as icon } from "./Icon.svelte"
export { button } from "./Button" export { button } from "./Button"
export { default as icon } from "./Icon.svelte"
export { textfield } from "./Textfield"
export * from "./Typography"
export { checkbox } from "./Checkbox"
// import { Button } from "./Button";
// import Icon from "./Icon.svelte";
// import { Textfield } from "./Textfield";
// export { Button, Icon, Textfield };
// export * from "./Typography";
// export { Checkbox } from "./Checkbox";