mirror of https://github.com/Budibase/budibase.git
Browse Source
* Completed checkbox and form field component with changes around how test app renders components * Tidyuppull/100/head
committed by
GitHub
13 changed files with 243 additions and 60 deletions
@ -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> |
|||
@ -0,0 +1,2 @@ |
|||
@import "@material/form-field/mdc-form-field"; |
|||
@import "@material/checkbox/mdc-checkbox.scss"; |
|||
@ -0,0 +1,2 @@ |
|||
import "./_style.scss"; |
|||
export { default as checkbox } from "./Checkbox.svelte"; |
|||
@ -1,38 +1,74 @@ |
|||
export default class ClassBuilder { |
|||
constructor(block, customDefaults) { |
|||
this.block = `mdc-${block}` |
|||
this.customDefaults = customDefaults //will be ignored when building custom classes
|
|||
constructor(block, defaultIgnoreList) { |
|||
this.block = `mdc-${block}`; |
|||
this.defaultIgnoreList = defaultIgnoreList; //will be ignored when building custom classes
|
|||
} |
|||
|
|||
// classParams: {modifiers:[] (mdc), custom:[] (bbmd), extra:[] (any)}
|
|||
blocks(classParams) { |
|||
let base = this.block |
|||
if (classParams == undefined) return base |
|||
return this.buildClass(base, classParams) |
|||
/* |
|||
handles both blocks and elementss (BEM MD Notation) |
|||
params = {elementName: string, props: {modifiers{}, customs:{}, extras: []}} |
|||
All are optional |
|||
*/ |
|||
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: {}
|
|||
elements(elementName, classParams) { |
|||
let base = `${this.block}__${elementName}` |
|||
if (classParams == undefined) return base |
|||
return this.buildClass(base, classParams) |
|||
//Easily grab a simple element class
|
|||
elem(elementName) { |
|||
return this.build({ elementName }); |
|||
} |
|||
|
|||
buildClass(base, classParams) { |
|||
let cls = base |
|||
const { modifiers, customs, extras } = classParams |
|||
if (modifiers) cls += modifiers.map(m => ` ${base}--${m}`).join(" ") |
|||
if (customs) |
|||
cls += Object.entries(customs) |
|||
.map(([property, value]) => { |
|||
//disregard falsy and values set by customDefaults constructor param
|
|||
if (!!value && !this.customDefaults.includes(value)) { |
|||
//custom scss name convention = bbmd-[block | element]--[property]-[value]
|
|||
return ` bbmd-${base}--${property}-${value}` |
|||
//use if a different base is needed than whats defined by this.block
|
|||
debase(base, elementProps) { |
|||
if (!elementProps) return base; |
|||
return this._handleProps(base, elementProps); |
|||
} |
|||
|
|||
//proxies bindProps and checks for which elementProps exist before binding
|
|||
_handleProps(base, elementProps) { |
|||
let cls = base; |
|||
const { modifiers, customs, extras } = elementProps; |
|||
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(" ")}` |
|||
return cls.trim() |
|||
} |
|||
}) |
|||
.join(""); |
|||
} |
|||
|
|||
_convertCamel(str) { |
|||
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`); |
|||
} |
|||
} |
|||
|
|||
@ -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> |
|||
@ -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(); |
|||
@ -1,17 +1,24 @@ |
|||
import { createApp } from "@budibase/client/src/createApp" |
|||
import components from "./testComponents" |
|||
import packageJson from "../../package.json" |
|||
|
|||
export default async () => { |
|||
import { rootComponent } from "./rootComponent" |
|||
export default async props => { |
|||
delete components._lib |
|||
|
|||
const componentLibraries = {} |
|||
componentLibraries[packageJson.name] = components |
|||
|
|||
componentLibraries["testcomponents"] = { |
|||
rootComponent: rootComponent(window) |
|||
} |
|||
const appDef = { hierarchy: {}, actions: {} } |
|||
const user = { name: "yeo", permissions: [] } |
|||
|
|||
var app = createApp(window.document, componentLibraries, appDef, user, {}) |
|||
|
|||
return app |
|||
} |
|||
const { initialisePage } = createApp( |
|||
window.document, |
|||
componentLibraries, |
|||
{ appRootPath: "" }, |
|||
appDef, |
|||
user, |
|||
{}, |
|||
[] |
|||
) |
|||
return initialisePage |
|||
} |
|||
@ -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) |
|||
} |
|||
} |
|||
@ -1,4 +1,3 @@ |
|||
import h1 from "../H1.svelte" |
|||
import { button, icon } from "@BBMD" |
|||
import { H1, Overline, button, icon, checkbox, textfield } from "@BBMD" |
|||
|
|||
export default { h1, button, icon } |
|||
export default {H1, Overline, button, icon, checkbox, textfield } |
|||
|
|||
@ -1,3 +1,14 @@ |
|||
export { default as h1 } from "./H1.svelte" |
|||
export { default as icon } from "./Icon.svelte" |
|||
import "@material/theme/mdc-theme.scss"; |
|||
|
|||
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";
|
|||
|
|||
Loading…
Reference in new issue