Browse Source

Merge pull request #253 from Budibase/property-panel/master

Complete CSS State Styles and Property Panel Structure
gh-pages
Conor_Mack 6 years ago
committed by GitHub
parent
commit
c69554e86e
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      packages/builder/package.json
  2. 174
      packages/builder/src/builderStore/generate_css.js
  3. 5
      packages/builder/src/builderStore/store/index.js
  4. 139
      packages/builder/src/components/common/Colorpicker.svelte
  5. 5
      packages/builder/src/components/common/Input.svelte
  6. 60
      packages/builder/src/components/common/Inputs/InputGroup.svelte
  7. 17
      packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte
  8. 3
      packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js
  9. 43
      packages/builder/src/components/userInterface/CategoryTab.svelte
  10. 218
      packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte
  11. 16
      packages/builder/src/components/userInterface/ComponentSelectionList.svelte
  12. 10
      packages/builder/src/components/userInterface/ComponentsPaneSwitcher.svelte
  13. 71
      packages/builder/src/components/userInterface/DesignView.svelte
  14. 38
      packages/builder/src/components/userInterface/FlatButton.svelte
  15. 54
      packages/builder/src/components/userInterface/FlatButtonGroup.svelte
  16. 36
      packages/builder/src/components/userInterface/OptionSelect.svelte
  17. 63
      packages/builder/src/components/userInterface/PropertyControl.svelte
  18. 81
      packages/builder/src/components/userInterface/PropertyGroup.svelte
  19. 41
      packages/builder/src/components/userInterface/SettingsView.svelte
  20. 240
      packages/builder/src/components/userInterface/UserInterfaceRoot.svelte
  21. 4
      packages/builder/src/components/userInterface/pagesParsing/createProps.js
  22. 173
      packages/builder/src/components/userInterface/propertyCategories.js
  23. 168
      packages/builder/src/components/userInterface/temporaryPanelStructure.js
  24. 14
      packages/builder/src/pages/[application]/backend/_layout.svelte
  25. 325
      packages/builder/tests/generate_css.spec.js
  26. 6
      packages/cli/src/commands/new/appDirectoryTemplate/pages/main/page.json
  27. 6
      packages/cli/src/commands/new/appDirectoryTemplate/pages/unauthenticated/page.json
  28. 6
      packages/cli/src/commands/new/page.template.json
  29. 2
      packages/client/src/render/attachChildren.js
  30. 3
      packages/client/src/render/prepareRenderComponent.js
  31. 132
      packages/standard-components/components.json
  32. 56
      packages/standard-components/src/Button.svelte
  33. 98
      packages/standard-components/src/Container.svelte
  34. 19
      packages/standard-components/src/Heading.svelte
  35. 38
      packages/standard-components/src/Link.svelte
  36. 62
      packages/standard-components/src/Text.svelte

3
packages/builder/package.json

@ -79,7 +79,8 @@
"rollup-plugin-svelte": "^5.0.3", "rollup-plugin-svelte": "^5.0.3",
"rollup-plugin-terser": "^4.0.4", "rollup-plugin-terser": "^4.0.4",
"rollup-plugin-url": "^2.2.2", "rollup-plugin-url": "^2.2.2",
"svelte": "^3.0.0" "svelte": "^3.0.0",
"svelte-color-picker": "^1.0.7"
}, },
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072" "gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
} }

174
packages/builder/src/builderStore/generate_css.js

@ -1,142 +1,54 @@
import { pipe } from "components/common/core" export const generate_screen_css = component_arr => {
import { filter, map, reduce, toPairs } from "lodash/fp" let styles = ""
for (const { _styles, _id, _children, _component } of component_arr) {
const self = n => n let [componentName] = _component.match(/[a-z]*$/)
const join_with = delimiter => a => a.join(delimiter) Object.keys(_styles).forEach(selector => {
const empty_string_to_unset = s => (s.length ? s : "0") const cssString = generate_css(_styles[selector])
const add_suffix_if_number = suffix => s => { if (cssString) {
try { styles += apply_class(_id, componentName, cssString, selector)
if (isNaN(s) || isNaN(parseFloat(s))) return s }
} catch (_) { })
return s if (_children && _children.length) {
styles += generate_screen_css(_children) + "\n"
}
} }
return s + suffix return styles.trim()
}
export const make_margin = values =>
pipe(values, [
map(empty_string_to_unset),
map(add_suffix_if_number("px")),
join_with(" "),
])
const css_map = {
templaterows: {
name: "grid-template-rows",
generate: self,
},
templatecolumns: {
name: "grid-template-columns",
generate: self,
},
align: {
name: "align-items",
generate: self,
},
justify: {
name: "justify-content",
generate: self,
},
direction: {
name: "flex-direction",
generate: self,
},
gridarea: {
name: "grid-area",
generate: make_margin,
},
gap: {
name: "grid-gap",
generate: n => `${n}px`,
},
columnstart: {
name: "grid-column-start",
generate: self,
},
columnend: {
name: "grid-column-end",
generate: self,
},
rowstart: {
name: "grid-row-start",
generate: self,
},
rowend: {
name: "grid-row-end",
generate: self,
},
padding: {
name: "padding",
generate: make_margin,
},
margin: {
name: "margin",
generate: make_margin,
},
zindex: {
name: "z-index",
generate: self,
},
height: {
name: "height",
generate: self,
},
width: {
name: "width",
generate: self,
},
} }
export const generate_rule = ([name, values]) => export const generate_css = style => {
`${css_map[name].name}: ${css_map[name].generate(values)};` let cssString = Object.entries(style).reduce((str, [key, value]) => {
//TODO Handle arrays and objects here also
const handle_grid = (acc, [name, value]) => { if (typeof value === "string") {
let tmp = [] if (value) {
return (str += `${key}: ${value};\n`)
if (name === "row" || name === "column") { }
if (value[0]) tmp.push([`${name}start`, value[0]]) } else if (Array.isArray(value)) {
if (value[1]) tmp.push([`${name}end`, value[1]]) if (value.length > 0 && !value.every(v => v === "")) {
return acc.concat(tmp) return (str += `${key}: ${value
} .map(generate_array_styles)
.join(" ")};\n`)
}
}
}, "")
return acc.concat([[name, value]]) return (cssString || "").trim()
} }
const object_to_css_string = [ export const generate_array_styles = item => {
toPairs, let safeItem = item === "" ? 0 : item
reduce(handle_grid, []), let hasPx = new RegExp("px$")
filter(v => (Array.isArray(v[1]) ? v[1].some(s => s.length) : v[1].length)), if (!hasPx.test(safeItem)) {
map(generate_rule), return `${safeItem}px`
join_with("\n"), } else {
] return safeItem
export const generate_css = ({ layout, position }) => {
let _layout = pipe(layout, object_to_css_string)
if (_layout.length) {
_layout += `\ndisplay: ${_layout.includes("flex") ? "flex" : "grid"};`
}
return {
layout: _layout,
position: pipe(position, object_to_css_string),
} }
} }
const apply_class = (id, name, styles) => `.${name}-${id} {\n${styles}\n}` export const apply_class = (id, name = "element", styles, selector) => {
if (selector === "normal") {
export const generate_screen_css = component_array => { return `.${name}-${id} {\n${styles}\n}`
let styles = "" } else {
let emptyStyles = { layout: {}, position: {} } let sel = selector === "selected" ? "::selection" : `:${selector}`
return `.${name}-${id}${sel} {\n${styles}\n}`
for (let i = 0; i < component_array.length; i += 1) {
const { _styles, _id, _children } = component_array[i]
const { layout, position } = generate_css(_styles || emptyStyles)
styles += apply_class(_id, "pos", position) + "\n"
styles += apply_class(_id, "lay", layout) + "\n"
if (_children && _children.length) {
styles += generate_screen_css(_children) + "\n"
}
} }
return styles.trim()
} }

5
packages/builder/src/builderStore/store/index.js

@ -140,10 +140,11 @@ const _saveScreen = async (store, s, screen) => {
return s return s
} }
const _saveScreenApi = (screen, s) => const _saveScreenApi = (screen, s) => {
api api
.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen) .post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
.then(() => _savePage(s)) .then(() => _savePage(s))
}
const createScreen = store => (screenName, route, layoutComponentName) => { const createScreen = store => (screenName, route, layoutComponentName) => {
store.update(state => { store.update(state => {
@ -278,7 +279,6 @@ const removeStylesheet = store => stylesheet => {
const _savePage = async s => { const _savePage = async s => {
const page = s.pages[s.currentPageName] const page = s.pages[s.currentPageName]
await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, { await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, {
page: { componentLibraries: s.pages.componentLibraries, ...page }, page: { componentLibraries: s.pages.componentLibraries, ...page },
uiFunctions: s.currentPageFunctions, uiFunctions: s.currentPageFunctions,
@ -427,6 +427,7 @@ const setComponentStyle = store => (type, name, value) => {
state.currentComponentInfo._styles = {} state.currentComponentInfo._styles = {}
} }
state.currentComponentInfo._styles[type][name] = value state.currentComponentInfo._styles[type][name] = value
state.currentPreviewItem._css = generate_screen_css([ state.currentPreviewItem._css = generate_screen_css([
state.currentPreviewItem.props, state.currentPreviewItem.props,
]) ])

139
packages/builder/src/components/common/Colorpicker.svelte

@ -1,13 +1,78 @@
<script> <script>
import { onMount, beforeUpdate, afterUpdate } from "svelte" import { onMount } from "svelte"
import { HsvPicker } from "svelte-color-picker"
export let value = null // export let initialValue = "#ffffff"
export let onChanged = () => {} export let onChange = color => {}
export let swatches = [] export let open = false
let value = "#ffffff"
let picker let _justMounted = true //see onColorChange
let cp = null let pickerHeight = 275
let colorPreview
let pickerTopPosition = null
function rbgaToHexa({ r, g, b, a }) {
r = r.toString(16)
g = g.toString(16)
b = b.toString(16)
a = Math.round(a * 255).toString(16)
if (r.length == 1) r = "0" + r
if (g.length == 1) g = "0" + g
if (b.length == 1) b = "0" + b
if (a.length == 1) a = "0" + a
return "#" + r + g + b + a
}
function onColourChange(rgba) {
value = rbgaToHexa(rgba.detail)
//Hack: so that color change doesn't fire onMount
if (!_justMounted) {
// onChange(value)
}
_justMounted = false
}
function toggleColorpicker(isOpen) {
if (isOpen) {
const {
y: previewYPosition,
height: previewHeight,
} = colorPreview.getBoundingClientRect()
let wiggleRoom = window.innerHeight - previewYPosition
let displayTop = wiggleRoom < pickerHeight
if (displayTop) {
pickerTopPosition = previewYPosition - (pickerHeight - window.scrollY)
} else {
pickerTopPosition = null
}
}
open = isOpen
}
$: style = open ? "display: block;" : "display: none;"
$: pickerStyle = pickerTopPosition ? `top: ${pickerTopPosition}px;` : ""
</script>
<div
bind:this={colorPreview}
on:click={() => toggleColorpicker(true)}
class="color-preview"
style={`background: ${value}`} />
<div class="colorpicker" {style}>
<div class="overlay" on:click|self={() => toggleColorpicker(false)} />
<div class="cp" style={pickerStyle}>
<HsvPicker on:colorChange={onColourChange} startColor={value} />
</div>
</div>
<!--
OLD LOCAL STORAGE OPTIONS. INCLUDING FOR ADDING LATER
function getRecentColors() { function getRecentColors() {
let colorStore = localStorage.getItem("bb:recentColors") let colorStore = localStorage.getItem("bb:recentColors")
if (!!colorStore) { if (!!colorStore) {
@ -25,47 +90,27 @@
picker.addSwatch(color) picker.addSwatch(color)
localStorage.setItem("bb:recentColors", JSON.stringify(swatches)) localStorage.setItem("bb:recentColors", JSON.stringify(swatches))
} }
} } -->
function createPicker() { <style>
picker = Pickr.create({ .overlay {
el: cp, position: absolute;
theme: "nano", top: 0;
default: value || "#000000", left: 0;
right: 0;
swatches, bottom: 0;
closeWithKey: "Escape", /* background: rgba(5, 5, 5, 0.25); */
components: {
preview: true,
opacity: true,
hue: true,
interaction: {
hex: true,
rgba: true,
input: true,
save: true,
},
},
})
} }
afterUpdate(() => { .cp {
picker.setColor(value) position: absolute;
}) right: 25px;
}
onMount(() => { .color-preview {
getRecentColors() height: 30px;
createPicker() width: 100%;
margin: 5px;
picker.on("save", (colour, instance) => { cursor: pointer;
let color = colour.toHEXA().toString() border: 1px solid gainsboro;
onChanged(color) }
setRecentColor(color) </style>
picker.hide()
})
})
</script>
<div bind:this={cp} class="color-picker" />

5
packages/builder/src/components/common/Input.svelte

@ -1,8 +1,11 @@
<script> <script>
export let value = "" export let value = ""
export let width = ""
let style = { width }
</script> </script>
<input type="text" on:change bind:value /> <input type="text" style={`width: ${width};`} on:change bind:value />
<style> <style>
input { input {

60
packages/builder/src/components/common/Inputs/InputGroup.svelte

@ -2,36 +2,50 @@
import { onMount } from "svelte" import { onMount } from "svelte"
export let meta = [] export let meta = []
export let size = "" export let label = ""
export let values = [] export let value = [0, 0, 0, 0]
export let type = "number" export let type = "number"
export let onStyleChanged = () => {} export let onChange = () => {}
let _values = values.map(v => v) function handleChange(val, idx) {
value.splice(idx, 1, val)
$: onStyleChanged(_values) value = value
onChange(value)
}
</script> </script>
<div class="inputs {size}"> <div class="input-container">
{#each meta as { placeholder }, i} <div class="label">{label}</div>
<input <div class="inputs">
{type} {#each meta as { placeholder }, i}
{placeholder} <input
value={values[i]} {type}
on:input={e => (_values[i] = e.target.value)} /> placeholder={placeholder || ''}
{/each} value={!value || value[i] === 0 ? '' : value[i]}
on:change={e => handleChange(e.target.value || 0, i)} />
{/each}
</div>
</div> </div>
<style> <style>
.inputs { .input-container {
display: flex; display: flex;
justify-content: space-between; }
.label {
flex: 0;
}
.inputs {
flex: 1;
} }
input { input {
width: 83px; width: 40px;
height: 32px;
font-size: 13px; font-size: 13px;
font-weight: 700; font-weight: 700;
margin: 0px 5px;
color: #163057; color: #163057;
opacity: 0.7; opacity: 0.7;
padding: 5px 10px; padding: 5px 10px;
@ -49,17 +63,11 @@
margin: 0; margin: 0;
} }
.small > input { input[type="number"] {
width: 38px; -moz-appearance: textfield;
height: 38px;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
padding: 0;
} }
.small > input::placeholder { input::placeholder {
text-align: center; text-align: center;
} }
</style> </style>

17
packages/builder/src/components/userInterface/AppPreview/CurrentItemPreview.svelte

@ -16,6 +16,11 @@
return props return props
} }
const getComponentTypeName = component => {
let [componentName] = component._component.match(/[a-z]*$/)
return componentName || "element"
}
$: iframe && $: iframe &&
console.log( console.log(
iframe.contentDocument.head.insertAdjacentHTML( iframe.contentDocument.head.insertAdjacentHTML(
@ -60,7 +65,7 @@
_children: [ _children: [
{ {
_component: "@budibase/standard-components/container", _component: "@budibase/standard-components/container",
_styles: { position: {}, layout: {} }, _styles: { normal: {}, hover: {}, active: {}, selected: {} },
_id: "__screenslot__text", _id: "__screenslot__text",
_code: "", _code: "",
className: "", className: "",
@ -69,7 +74,12 @@
_children: [ _children: [
{ {
_component: "@budibase/standard-components/text", _component: "@budibase/standard-components/text",
_styles: { position: {}, layout: {} }, _styles: {
normal: {},
hover: {},
active: {},
selected: {},
},
_id: "__screenslot__text_2", _id: "__screenslot__text_2",
_code: "", _code: "",
text: "content", text: "content",
@ -88,6 +98,8 @@
appRootPath: "", appRootPath: "",
} }
$: selectedComponentType = getComponentTypeName($store.currentComponentInfo)
$: selectedComponentId = $store.currentComponentInfo $: selectedComponentId = $store.currentComponentInfo
? $store.currentComponentInfo._id ? $store.currentComponentInfo._id
: "" : ""
@ -102,6 +114,7 @@
srcdoc={iframeTemplate({ srcdoc={iframeTemplate({
styles, styles,
stylesheetLinks, stylesheetLinks,
selectedComponentType,
selectedComponentId, selectedComponentId,
frontendDefinition: JSON.stringify(frontendDefinition), frontendDefinition: JSON.stringify(frontendDefinition),
currentPageFunctions: $store.currentPageFunctions, currentPageFunctions: $store.currentPageFunctions,

3
packages/builder/src/components/userInterface/AppPreview/iframeTemplate.js

@ -1,6 +1,7 @@
export default ({ export default ({
styles, styles,
stylesheetLinks, stylesheetLinks,
selectedComponentType,
selectedComponentId, selectedComponentId,
frontendDefinition, frontendDefinition,
currentPageFunctions, currentPageFunctions,
@ -11,7 +12,7 @@ export default ({
<style> <style>
${styles || ""} ${styles || ""}
.pos-${selectedComponentId} { .${selectedComponentType}-${selectedComponentId} {
border: 2px solid #0055ff; border: 2px solid #0055ff;
} }

43
packages/builder/src/components/userInterface/CategoryTab.svelte

@ -0,0 +1,43 @@
<script>
export let categories = []
export let selectedCategory = {}
export let onClick = category => {}
</script>
<ul class="tabs">
{#each categories as category}
<li
on:click={() => onClick(category)}
class:active={selectedCategory === category}>
{category.name}
</li>
{/each}
</ul>
<style>
.tabs {
display: flex;
justify-content: center;
list-style: none;
margin: 0 auto;
padding: 0 30px;
border-bottom: 1px solid #d8d8d8;
font-size: 14px;
font-weight: 500;
letter-spacing: 0.14px;
}
li {
color: #808192;
margin: 0 5px;
padding: 0 8px;
cursor: pointer;
}
.active {
border-bottom: solid 3px #0055ff;
color: #393c44;
}
</style>

218
packages/builder/src/components/userInterface/ComponentPropertiesPanel.svelte

@ -1,4 +1,5 @@
<script> <script>
import { setContext, onMount } from "svelte"
import PropsView from "./PropsView.svelte" import PropsView from "./PropsView.svelte"
import { store } from "builderStore" import { store } from "builderStore"
import IconButton from "components/common/IconButton.svelte" import IconButton from "components/common/IconButton.svelte"
@ -13,23 +14,63 @@
import LayoutEditor from "./LayoutEditor.svelte" import LayoutEditor from "./LayoutEditor.svelte"
import EventsEditor from "./EventsEditor" import EventsEditor from "./EventsEditor"
let current_view = "props" import panelStructure from "./temporaryPanelStructure.js"
import CategoryTab from "./CategoryTab.svelte"
import DesignView from "./DesignView.svelte"
import SettingsView from "./SettingsView.svelte"
let current_view = "design"
let codeEditor let codeEditor
let flattenedPanel = flattenComponents(panelStructure.categories)
let categories = [
{ value: "design", name: "Design" },
{ value: "settings", name: "Settings" },
{ value: "actions", name: "Actions" },
]
let selectedCategory = categories[0]
$: component = $store.currentComponentInfo
$: originalName = component.name
$: name =
$store.currentView === "detail"
? $store.currentPreviewItem.name
: component._component
$: description = component.description
$: components = $store.components $: components = $store.components
$: componentInstance = $store.currentComponentInfo
$: componentDefinition = $store.components[componentInstance._component]
$: componentPropDefinition =
flattenedPanel.find(
//use for getting controls for each component property
c => c._component === componentInstance._component
) || {}
$: panelDefinition = componentPropDefinition.properties
? componentPropDefinition.properties[selectedCategory.value]
: {}
// SCREEN PROPS =============================================
$: screen_props = $: screen_props =
$store.currentFrontEndType === "page" $store.currentFrontEndType === "page"
? getProps($store.currentPreviewItem, ["name", "favicon"]) ? getProps($store.currentPreviewItem, ["name", "favicon"])
: getProps($store.currentPreviewItem, ["name", "description", "route"]) : getProps($store.currentPreviewItem, ["name", "description", "route"])
const onStyleChanged = store.setComponentStyle const onStyleChanged = store.setComponentStyle
const onPropChanged = store.setComponentProp
function walkProps(component, action) {
action(component)
if (component.children) {
for (let child of component.children) {
walkProps(child, action)
}
}
}
function flattenComponents(props) {
const components = []
props.forEach(comp =>
walkProps(comp, c => {
if ("_component" in c) {
components.push(c)
}
})
)
return components
}
function getProps(obj, keys) { function getProps(obj, keys) {
return keys.map((key, i) => [key, obj[key], obj.props._id + i]) return keys.map((key, i) => [key, obj[key], obj.props._id + i])
@ -37,116 +78,33 @@
</script> </script>
<div class="root"> <div class="root">
<ul>
<li>
<button
class:selected={current_view === 'props'}
on:click={() => (current_view = 'props')}>
<PaintIcon />
</button>
</li>
<li>
<button
class:selected={current_view === 'layout'}
on:click={() => (current_view = 'layout')}>
<LayoutIcon />
</button>
</li>
{#if !component._component.startsWith('##')}
<li>
<button
class:selected={current_view === 'code'}
on:click={() => codeEditor && codeEditor.show()}>
{#if component._code && component._code.trim().length > 0}
<div class="button-indicator">
<CircleIndicator />
</div>
{/if}
<TerminalIcon />
</button>
</li>
<li>
<button
class:selected={current_view === 'events'}
on:click={() => (current_view = 'events')}>
<EventsIcon />
</button>
</li>
{/if}
</ul>
<div class="component-props-container">
{#if current_view === 'props'} <CategoryTab
{#if $store.currentView === 'detail'} onClick={category => (selectedCategory = category)}
{#each screen_props as [k, v, id] (id)} {categories}
<div class="detail-prop" for={k}> {selectedCategory} />
<label>{k}:</label>
<input
id={k}
value={v}
on:input={({ target }) => store.setMetadataProp(k, target.value)} />
</div>
{/each}
<PropsView {component} {components} />
{:else}
<PropsView {component} {components} />
{/if}
{:else if current_view === 'layout'}
<LayoutEditor {onStyleChanged} {component} />
{:else if current_view === 'events'}
<EventsEditor {component} {components} />
{/if}
<CodeEditor <div class="component-props-container">
bind:this={codeEditor} {#if selectedCategory.value === 'design'}
code={component._code} <DesignView {panelDefinition} {componentInstance} {onStyleChanged} />
onCodeChanged={store.setComponentCode} /> {:else if selectedCategory.value === 'settings'}
<SettingsView
{componentInstance}
{componentDefinition}
{panelDefinition}
onChange={onPropChanged} />
{/if}
</div> </div>
</div> </div>
<style> <style>
.detail-prop {
height: 40px;
margin-bottom: 15px;
display: grid;
grid-template-rows: 1fr;
grid-template-columns: 70px 1fr;
grid-gap: 10px;
}
.detail-prop label {
word-wrap: break-word;
font-size: 13px;
font-weight: 700;
color: #163057;
opacity: 0.6;
padding-top: 13px;
margin-bottom: 0;
}
input {
height: 30px;
padding-left: 8px;
padding-right: 8px;
border: 1px solid #dbdbdb;
border-radius: 2px;
opacity: 0.5;
}
input:focus {
outline: 0;
background-color: #fff;
color: #666;
border-color: #1e87f0;
}
.root { .root {
height: 100%; height: 100%;
padding: 20px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow-x: hidden;
} }
.title > div:nth-child(1) { .title > div:nth-child(1) {
@ -162,52 +120,4 @@
margin-top: 10px; margin-top: 10px;
flex: 1 1 auto; flex: 1 1 auto;
} }
ul {
list-style: none;
display: flex;
justify-content: space-between;
padding: 0;
}
li {
background: none;
border-radius: 3px;
width: 48px;
height: 48px;
}
li button {
width: 48px;
height: 48px;
background: none;
border: none;
border-radius: 3px;
padding: 7px;
outline: none;
cursor: pointer;
position: relative;
}
li:nth-last-child(1) {
margin-right: 0px;
background: none;
border-radius: 3px;
width: 48px;
height: 48px;
}
.selected {
color: var(--button-text);
background: #f9f9f9 !important;
width: 48px;
height: 48px;
}
.button-indicator {
position: absolute;
top: 8px;
right: 10px;
color: var(--button-text);
}
</style> </style>

16
packages/builder/src/components/userInterface/ComponentSelectionList.svelte

@ -2,6 +2,7 @@
import { splitName } from "./pagesParsing/splitRootComponentName.js" import { splitName } from "./pagesParsing/splitRootComponentName.js"
import components from "./temporaryPanelStructure.js" import components from "./temporaryPanelStructure.js"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import CategoryTab from "./CategoryTab.svelte"
import { import {
find, find,
sortBy, sortBy,
@ -36,15 +37,12 @@
</script> </script>
<div class="root"> <div class="root">
<ul class="tabs">
{#each categories as category} <CategoryTab
<li onClick={category => (selectedCategory = category)}
on:click={() => (selectedCategory = category)} {selectedCategory}
class:active={selectedCategory === category}> {categories} />
{category.name}
</li>
{/each}
</ul>
<div class="panel"> <div class="panel">
<Tab <Tab
list={selectedCategory} list={selectedCategory}

10
packages/builder/src/components/userInterface/ComponentsPaneSwitcher.svelte

@ -30,7 +30,7 @@
<button <button
class:selected={selected === PROPERTIES_TAB} class:selected={selected === PROPERTIES_TAB}
on:click={() => selectTab(PROPERTIES_TAB)}> on:click={() => selectTab(PROPERTIES_TAB)}>
Properties Edit
</button> </button>
</div> </div>
@ -50,6 +50,14 @@
</div> </div>
<style> <style>
.root {
height: 100%;
display: flex;
flex-direction: column;
padding: 20px 0;
border-left: solid 1px #e8e8ef;
}
.switcher { .switcher {
display: flex; display: flex;
margin: 0px 20px 20px 20px; margin: 0px 20px 20px 20px;

71
packages/builder/src/components/userInterface/DesignView.svelte

@ -0,0 +1,71 @@
<script>
import PropertyGroup from "./PropertyGroup.svelte"
import FlatButtonGroup from "./FlatButtonGroup.svelte"
export let panelDefinition = {}
export let componentInstance = {}
export let componentDefinition = {}
export let onStyleChanged = () => {}
let selectedCategory = "normal"
const getProperties = name => panelDefinition[name]
function onChange(category) {
selectedCategory = category
}
const buttonProps = [
{ value: "normal", text: "Normal" },
{ value: "hover", text: "Hover" },
{ value: "active", text: "Active" },
{ value: "selected", text: "Selected" },
]
$: propertyGroupNames = Object.keys(panelDefinition)
</script>
<div class="design-view-container">
<div class="design-view-state-categories">
<FlatButtonGroup value={selectedCategory} {buttonProps} {onChange} />
</div>
<div class="design-view-property-groups">
{#if propertyGroupNames.length > 0}
{#each propertyGroupNames as groupName}
<PropertyGroup
name={groupName}
properties={getProperties(groupName)}
styleCategory={selectedCategory}
{onStyleChanged}
{componentDefinition}
{componentInstance} />
{/each}
{:else}
<div class="no-design">
<span>This component does not have any design properties</span>
</div>
{/if}
</div>
</div>
<style>
.design-view-container {
display: flex;
flex-direction: column;
width: 100%;
}
.design-view-state-categories {
flex: 0 0 50px;
}
.design-view-property-groups {
flex: 1;
}
.no-design {
text-align: center;
}
</style>

38
packages/builder/src/components/userInterface/FlatButton.svelte

@ -0,0 +1,38 @@
<script>
export let value = ""
export let text = ""
export let icon = ""
export let onClick = value => {}
export let selected = false
$: useIcon = !!icon
</script>
<div class="flatbutton" class:selected on:click={() => onClick(value || text)}>
{#if useIcon}
<i class={icon} />
{:else}
<span>{text}</span>
{/if}
</div>
<style>
.flatbutton {
cursor: pointer;
padding: 5px;
text-align: center;
background: #ffffff;
color: #808192;
border-radius: 4px;
font-family: Roboto;
font-size: 11px;
font-weight: 500;
letter-spacing: 0.11px;
transition: background 0.5s, color 0.5s ease;
}
.selected {
background: #808192;
color: #ffffff;
}
</style>

54
packages/builder/src/components/userInterface/FlatButtonGroup.svelte

@ -0,0 +1,54 @@
<script>
import { onMount } from "svelte"
import FlatButton from "./FlatButton.svelte"
export let buttonProps = []
export let isMultiSelect = false
export let value = []
export let initialValue = ""
export let onChange = selected => {}
onMount(() => {
if (!value && !!initialValue) {
value = initialValue
}
})
function onButtonClicked(v) {
let val
if (isMultiSelect) {
if (value.includes(v)) {
let idx = value.findIndex(i => i === v)
val = [...value].splice(idx, 1)
} else {
val = [...value, v]
}
} else {
val = v
}
onChange(val)
}
</script>
<div class="flatbutton-group">
{#each buttonProps as props}
<div class="button-container">
<FlatButton
selected={value.includes(props.value)}
onClick={onButtonClicked}
{...props} />
</div>
{/each}
</div>
<style>
.flatbutton-group {
display: flex;
flex-flow: row nowrap;
}
.button-container {
flex: 1;
margin: 5px;
}
</style>

36
packages/builder/src/components/userInterface/OptionSelect.svelte

@ -0,0 +1,36 @@
<script>
import { onMount } from "svelte"
export let value = ""
export let onChange = value => {}
export let options = []
export let initialValue = ""
export let styleBindingProperty = ""
const handleStyleBind = value =>
!!styleBindingProperty ? { style: `${styleBindingProperty}: ${value}` } : {}
$: isOptionsObject = options.every(o => typeof o === "object")
onMount(() => {
if (!value && !!initialValue) {
value = initialValue
}
})
</script>
<select
class="uk-select uk-form-small"
{value}
on:change={ev => onChange(ev.target.value)}>
{#if isOptionsObject}
{#each options as { value, label }}
<option {...handleStyleBind(value || label)} value={value || label}>
{label}
</option>
{/each}
{:else}
{#each options as value}
<option {...handleStyleBind(value)} {value}>{value}</option>
{/each}
{/if}
</select>

63
packages/builder/src/components/userInterface/PropertyControl.svelte

@ -0,0 +1,63 @@
<script>
import { onMount, getContext } from "svelte"
export let label = ""
export let control = null
export let key = ""
export let value
export let props = {}
export let onChange = () => {}
function handleChange(key, v) {
if (v.target) {
let val = props.valueKey ? v.target[props.valueKey] : v.target.value
onChange(key, val)
} else {
onChange(key, v)
}
}
const safeValue = () => {
return value === undefined && props.defaultValue !== undefined
? props.defaultValue
: value
}
//Incase the component has a different value key name
const handlevalueKey = value =>
props.valueKey ? { [props.valueKey]: safeValue() } : { value: safeValue() }
</script>
<div class="property-control">
<div class="label">{label}</div>
<div class="control">
<svelte:component
this={control}
{...handlevalueKey(value)}
on:change={val => handleChange(key, val)}
onChange={val => handleChange(key, val)}
{...props} />
</div>
</div>
<style>
.property-control {
display: flex;
flex-flow: row nowrap;
margin: 8px 0px;
}
.label {
flex: 0 0 50px;
padding: 0px 5px;
font-size: 12px;
font-weight: 500;
letter-spacing: 0.12px;
text-align: left;
}
.control {
flex: 1;
padding-left: 5px;
}
</style>

81
packages/builder/src/components/userInterface/PropertyGroup.svelte

@ -0,0 +1,81 @@
<script>
import { excludeProps } from "./propertyCategories.js"
import PropertyControl from "./PropertyControl.svelte"
export let name = ""
export let styleCategory = "normal"
export let properties = []
export let componentInstance = {}
export let onStyleChanged = () => {}
export let show = false
const capitalize = name => name[0].toUpperCase() + name.slice(1)
$: icon = show ? "ri-arrow-down-s-fill" : "ri-arrow-right-s-fill"
$: style = componentInstance["_styles"][styleCategory] || {}
</script>
<div class="property-group-container">
<div class="property-group-name" on:click={() => (show = !show)}>
<div class="icon">
<i class={icon} />
</div>
<div class="name">{capitalize(name)}</div>
</div>
<div class="property-panel" class:show>
{#each properties as props}
<PropertyControl
label={props.label}
control={props.control}
key={props.key}
value={style[props.key]}
onChange={(key, value) => onStyleChanged(styleCategory, key, value)}
props={{ ...excludeProps(props, ['control', 'label']) }} />
{/each}
</div>
</div>
<style>
.property-group-container {
display: flex;
flex-direction: column;
height: auto;
background: #fbfbfb;
margin: 5px;
padding: 5px;
}
.property-group-name {
cursor: pointer;
flex: 0 0 20px;
display: flex;
flex-flow: row nowrap;
}
.name {
flex: 1;
text-align: left;
padding-top: 2px;
font-size: 14px;
font-weight: 500;
letter-spacing: 0.14px;
color: #393c44;
}
.icon {
flex: 0 0 20px;
text-align: center;
}
.property-panel {
height: 0px;
overflow: hidden;
}
.show {
overflow: auto;
height: auto;
}
</style>

41
packages/builder/src/components/userInterface/SettingsView.svelte

@ -0,0 +1,41 @@
<script>
import PropertyControl from "./PropertyControl.svelte"
import InputGroup from "../common/Inputs/InputGroup.svelte"
import Colorpicker from "../common/Colorpicker.svelte"
import { excludeProps } from "./propertyCategories.js"
export let panelDefinition = []
export let componentDefinition = {}
export let componentInstance = {}
export let onChange = () => {}
const propExistsOnComponentDef = prop => prop in componentDefinition.props
function handleChange(key, data) {
data.target ? onChange(key, data.target.value) : onChange(key, data)
}
</script>
{#if panelDefinition.length > 0}
{#each panelDefinition as definition}
{#if propExistsOnComponentDef(definition.key)}
<PropertyControl
control={definition.control}
label={definition.label}
key={definition.key}
value={componentInstance[definition.key]}
{onChange}
props={{ ...excludeProps(definition, ['control', 'label']) }} />
{/if}
{/each}
{:else}
<div>
<span>This component does not have any settings.</span>
</div>
{/if}
<style>
div {
text-align: center;
}
</style>

240
packages/builder/src/components/userInterface/UserInterfaceRoot.svelte

@ -0,0 +1,240 @@
<script>
import ComponentsHierarchy from "./ComponentsHierarchy.svelte"
import ComponentsHierarchyChildren from "./ComponentsHierarchyChildren.svelte"
import PageLayout from "./PageLayout.svelte"
import PagesList from "./PagesList.svelte"
import { store } from "builderStore"
import IconButton from "components/common/IconButton.svelte"
import NewScreen from "./NewScreen.svelte"
import CurrentItemPreview from "./CurrentItemPreview.svelte"
import SettingsView from "./SettingsView.svelte"
import PageView from "./PageView.svelte"
import ComponentsPaneSwitcher from "./ComponentsPaneSwitcher.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { last } from "lodash/fp"
import { AddIcon } from "components/common/Icons"
let newScreenPicker
let confirmDeleteDialog
let componentToDelete = ""
const newScreen = () => {
newScreenPicker.show()
}
let settingsView
const settings = () => {
settingsView.show()
}
const confirmDeleteComponent = component => {
componentToDelete = component
confirmDeleteDialog.show()
}
const lastPartOfName = c => (c ? last(c.split("/")) : "")
</script>
<div class="root">
<div class="ui-nav">
<div class="pages-list-container">
<div class="nav-header">
<span class="navigator-title">Navigator</span>
<div class="border-line" />
<span class="components-nav-page">Pages</span>
</div>
<div class="nav-items-container">
<PagesList />
</div>
</div>
<div class="border-line" />
<PageLayout layout={$store.pages[$store.currentPageName]} />
<div class="border-line" />
<div class="components-list-container">
<div class="nav-group-header">
<span class="components-nav-header" style="margin-top: 0;">
Screens
</span>
<div>
<button on:click={newScreen}>
<AddIcon />
</button>
</div>
</div>
<div class="nav-items-container">
<ComponentsHierarchy screens={$store.screens} />
</div>
</div>
</div>
<div class="preview-pane">
<CurrentItemPreview />
</div>
{#if $store.currentFrontEndType === 'screen' || $store.currentFrontEndType === 'page'}
<div class="components-pane">
<ComponentsPaneSwitcher />
</div>
{/if}
</div>
<NewScreen bind:this={newScreenPicker} />
<SettingsView bind:this={settingsView} />
<ConfirmDialog
bind:this={confirmDeleteDialog}
title="Confirm Delete"
body={`Are you sure you wish to delete this '${lastPartOfName(componentToDelete)}' component`}
okText="Delete Component"
onOk={() => store.deleteComponent(componentToDelete)} />
<style>
button {
cursor: pointer;
outline: none;
border: none;
border-radius: 5px;
width: 20px;
padding-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
}
.root {
display: grid;
grid-template-columns: 275px 1fr 300px;
height: 100%;
width: 100%;
background: #fafafa;
}
@media only screen and (min-width: 1800px) {
.root {
display: grid;
grid-template-columns: 300px 1fr 300px;
height: 100%;
width: 100%;
background: #fafafa;
}
}
.ui-nav {
grid-column: 1;
background-color: var(--white);
height: calc(100vh - 49px);
padding: 0;
overflow: scroll;
display: flex;
flex-direction: column;
}
.preview-pane {
grid-column: 2;
margin: 40px;
background: #fff;
border-radius: 5px;
box-shadow: 0 0px 6px rgba(0, 0, 0, 0.05);
}
.components-pane {
grid-column: 3;
background-color: var(--white);
height: 100vh;
overflow-y: scroll;
}
.components-nav-page {
font-size: 13px;
color: #000333;
text-transform: uppercase;
padding-left: 20px;
margin-top: 20px;
font-weight: 600;
opacity: 0.4;
letter-spacing: 1px;
}
.components-nav-header {
font-size: 13px;
color: #000333;
text-transform: uppercase;
margin-top: 20px;
font-weight: 600;
opacity: 0.4;
letter-spacing: 1px;
}
.nav-header {
display: flex;
flex-direction: column;
margin-top: 20px;
}
.nav-items-container {
padding: 1rem 0rem 0rem 0rem;
}
.nav-group-header {
display: flex;
padding: 0px 20px 0px 20px;
font-size: 0.9rem;
font-weight: bold;
justify-content: space-between;
align-items: center;
}
.nav-group-header > div:nth-child(1) {
padding: 0rem 0.5rem 0rem 0rem;
vertical-align: bottom;
grid-column-start: icon;
margin-right: 5px;
}
.nav-group-header > span:nth-child(3) {
margin-left: 5px;
vertical-align: bottom;
grid-column-start: title;
margin-top: auto;
}
.nav-group-header > div:nth-child(3) {
vertical-align: bottom;
grid-column-start: button;
cursor: pointer;
color: var(--primary75);
}
.nav-group-header > div:nth-child(3):hover {
color: var(--primary75);
}
.navigator-title {
font-size: 14px;
color: var(--secondary100);
font-weight: 600;
text-transform: uppercase;
padding: 0 20px 20px 20px;
line-height: 1rem !important;
letter-spacing: 1px;
}
.border-line {
border-bottom: 1px solid #d8d8d8;
}
.components-list-container {
padding: 20px 0px 0 0;
}
</style>

4
packages/builder/src/components/userInterface/pagesParsing/createProps.js

@ -24,7 +24,7 @@ export const createProps = (componentDefinition, derivedFromProps) => {
const props = { const props = {
_id: uuid(), _id: uuid(),
_component: componentDefinition._component, _component: componentDefinition._component,
_styles: { position: {}, layout: {} }, _styles: { normal: {}, hover: {}, active: {}, selected: {} },
_code: "", _code: "",
} }
@ -71,7 +71,7 @@ export const makePropsSafe = (componentDefinition, props) => {
} }
if (!props._styles) { if (!props._styles) {
props._styles = { layout: {}, position: {} } props._styles = { normal: {}, hover: {}, active: {}, selected: {} }
} }
return props return props

173
packages/builder/src/components/userInterface/propertyCategories.js

@ -0,0 +1,173 @@
import Input from "../common/Input.svelte"
import OptionSelect from "./OptionSelect.svelte"
import InputGroup from "../common/Inputs/InputGroup.svelte"
// import Colorpicker from "../common/Colorpicker.svelte"
/*
TODO: Allow for default values for all properties
*/
export const layout = [
{
label: "Direction",
key: "flex-direction",
control: OptionSelect,
initialValue: "columnReverse",
options: [
{ label: "row" },
{ label: "row-reverse", value: "rowReverse" },
{ label: "column" },
{ label: "column-reverse", value: "columnReverse" },
],
},
{ label: "Justify", key: "justify-content", control: Input },
{ label: "Align", key: "align-items", control: Input },
{
label: "Wrap",
key: "flex-wrap",
control: OptionSelect,
options: [{ label: "wrap" }, { label: "no wrap", value: "noWrap" }],
},
]
const spacingMeta = [
{ placeholder: "T" },
{ placeholder: "R" },
{ placeholder: "B" },
{ placeholder: "L" },
]
export const spacing = [
{
label: "Padding",
key: "padding",
control: InputGroup,
meta: spacingMeta,
},
{ label: "Margin", key: "margin", control: InputGroup, meta: spacingMeta },
]
export const size = [
{ label: "Width", key: "width", control: Input },
{ label: "Height", key: "height", control: Input },
{ label: "Min W", key: "min-width", control: Input },
{ label: "Min H", key: "min-height", control: Input },
{ label: "Max W", key: "max-width", control: Input },
{ label: "Max H", key: "max-height", control: Input },
]
export const position = [
{
label: "Position",
key: "position",
control: OptionSelect,
options: [
{ label: "static" },
{ label: "relative" },
{ label: "fixed" },
{ label: "absolute" },
{ label: "sticky" },
],
},
]
export const typography = [
{
label: "Font",
key: "font-family",
control: OptionSelect,
defaultValue: "initial",
options: [
"initial",
"Times New Roman",
"Georgia",
"Arial",
"Arial Black",
"Comic Sans MS",
"Impact",
"Lucida Sans Unicode",
],
styleBindingProperty: "font-family",
},
{
label: "Weight",
key: "font-weight",
control: OptionSelect,
options: [
{ label: "normal" },
{ label: "bold" },
{ label: "bolder" },
{ label: "lighter" },
],
},
{ label: "size", key: "font-size", defaultValue: "", control: Input },
{ label: "Line H", key: "line-height", control: Input },
{
label: "Color",
key: "color",
control: OptionSelect,
options: ["black", "white", "red", "blue", "green"],
},
{
label: "align",
key: "text-align",
control: OptionSelect,
options: ["initial", "left", "right", "center", "justify"],
}, //custom
{ label: "transform", key: "text-transform", control: Input }, //custom
{ label: "style", key: "font-style", control: Input }, //custom
]
export const background = [
{
label: "Background",
key: "background",
control: OptionSelect,
options: ["black", "white", "red", "blue", "green"],
},
{ label: "Image", key: "image", control: Input }, //custom
]
export const border = [
{ label: "Radius", key: "border-radius", control: Input },
{ label: "Width", key: "border-width", control: Input }, //custom
{
label: "Color",
key: "border-color",
control: OptionSelect,
options: ["black", "white", "red", "blue", "green"],
},
{ label: "Style", key: "border-style", control: Input },
]
export const effects = [
{ label: "Opacity", key: "opacity", control: Input },
{ label: "Rotate", key: "transform", control: Input }, //needs special control
{ label: "Shadow", key: "box-shadow", control: Input },
]
export const transitions = [
{ label: "Property", key: "transition-property", control: Input },
{ label: "Duration", key: "transition-timing-function", control: Input },
{ label: "Ease", key: "transition-ease", control: Input },
]
export const all = {
layout,
spacing,
size,
position,
typography,
background,
border,
effects,
transitions,
}
export function excludeProps(props, propsToExclude) {
const modifiedProps = {}
for (const prop in props) {
if (!propsToExclude.includes(prop)) {
modifiedProps[prop] = props[prop]
}
}
return modifiedProps
}

168
packages/builder/src/components/userInterface/temporaryPanelStructure.js

@ -1,3 +1,9 @@
import Input from "../common/Input.svelte"
import OptionSelect from "./OptionSelect.svelte"
import Checkbox from "../common/Checkbox.svelte"
import { all } from "./propertyCategories.js"
export default { export default {
categories: [ categories: [
{ {
@ -20,6 +26,31 @@ export default {
icon: "ri-layout-row-fill", icon: "ri-layout-row-fill",
commonProps: {}, commonProps: {},
children: [], children: [],
properties: {
design: { ...all },
settings: [
{
key: "type",
label: "Type",
control: OptionSelect,
options: [
{ label: "article" },
{ label: "aside" },
{ label: "details" },
{ label: "div" },
{ label: "figure" },
{ label: "figcaption" },
{ label: "footer" },
{ label: "header" },
{ label: "main" },
{ label: "mark" },
{ label: "nav" },
{ label: "paragraph" },
{ label: "summary" },
],
},
],
},
}, },
{ {
name: "Text", name: "Text",
@ -32,13 +63,21 @@ export default {
name: "Headline", name: "Headline",
description: "A component for displaying heading text", description: "A component for displaying heading text",
icon: "ri-heading", icon: "ri-heading",
props: { properties: {
type: { design: { ...all },
type: "options", settings: [
options: ["h1", "h2", "h3", "h4", "h5", "h6"], {
default: "h1", key: "text",
}, label: "Text",
text: "string", control: Input,
},
{
key: "type",
label: "Type",
control: OptionSelect,
options: ["h1", "h2", "h3", "h4", "h5", "h6"],
},
],
}, },
}, },
{ {
@ -46,7 +85,34 @@ export default {
name: "Paragraph", name: "Paragraph",
description: "A component for displaying paragraph text.", description: "A component for displaying paragraph text.",
icon: "ri-paragraph", icon: "ri-paragraph",
props: {}, properties: {
design: { ...all },
settings: [
{
label: "Text",
key: "text",
control: Input,
},
{
label: "Type",
key: "type",
control: OptionSelect,
options: [
"none",
"bold",
"strong",
"italic",
"emphasis",
"mark",
"small",
"del",
"ins",
"sub",
"sup",
],
},
],
},
}, },
], ],
}, },
@ -62,21 +128,38 @@ export default {
description: description:
"A textfield component that allows the user to input text.", "A textfield component that allows the user to input text.",
icon: "ri-edit-box-line", icon: "ri-edit-box-line",
props: {}, properties: {
design: { ...all },
settings: [
{ label: "Label", key: "label", control: Input },
{
label: "Type",
key: "type",
control: OptionSelect,
options: ["text", "password"],
},
],
},
}, },
{ {
_component: "@budibase/standard-components/checkbox", _component: "@budibase/standard-components/checkbox",
name: "Checkbox", name: "Checkbox",
description: "A selectable checkbox component", description: "A selectable checkbox component",
icon: "ri-checkbox-line", icon: "ri-checkbox-line",
props: {}, properties: {
design: { ...all },
settings: [{ label: "Label", key: "label", control: Input }],
},
}, },
{ {
_component: "@budibase/standard-components/radiobutton", _component: "@budibase/standard-components/radiobutton",
name: "Radiobutton", name: "Radiobutton",
description: "A selectable radiobutton component", description: "A selectable radiobutton component",
icon: "ri-radio-button-line", icon: "ri-radio-button-line",
props: {}, properties: {
design: { ...all },
settings: [{ label: "Label", key: "label", control: Input }],
},
}, },
{ {
_component: "@budibase/standard-components/select", _component: "@budibase/standard-components/select",
@ -84,7 +167,10 @@ export default {
description: description:
"A select component for choosing from different options", "A select component for choosing from different options",
icon: "ri-file-list-line", icon: "ri-file-list-line",
props: {}, properties: {
design: { ...all },
settings: [],
},
}, },
], ],
}, },
@ -93,24 +179,51 @@ export default {
name: "Button", name: "Button",
description: "A basic html button that is ready for styling", description: "A basic html button that is ready for styling",
icon: "ri-radio-button-fill", icon: "ri-radio-button-fill",
commonProps: {},
children: [], children: [],
properties: {
design: {
...all,
},
settings: [
{ label: "Text", key: "text", control: Input },
{
label: "Disabled",
key: "disabled",
valueKey: "checked",
control: Checkbox,
},
],
},
}, },
{ {
_component: "@budibase/standard-components/icon", _component: "@budibase/standard-components/icon",
name: "Icon", name: "Icon",
description: "A basic component for displaying icons", description: "A basic component for displaying icons",
icon: "ri-sun-fill", icon: "ri-sun-fill",
commonProps: {},
children: [], children: [],
properties: {
design: { ...all },
},
}, },
{ {
_component: "@budibase/standard-components/link", _component: "@budibase/standard-components/link",
name: "Link", name: "Link",
description: "A basic link component for internal and external links", description: "A basic link component for internal and external links",
icon: "ri-link", icon: "ri-link",
commonProps: {},
children: [], children: [],
properties: {
design: { ...all },
settings: [
{ label: "Text", key: "text", control: Input },
{ label: "Url", key: "url", control: Input },
{
label: "Open New Tab",
key: "openInNewTab",
valueKey: "checked",
control: Checkbox,
},
],
},
}, },
], ],
}, },
@ -124,17 +237,16 @@ export default {
description: description:
"A basic card component that can contain content and actions.", "A basic card component that can contain content and actions.",
icon: "ri-layout-bottom-line", icon: "ri-layout-bottom-line",
commonProps: {},
children: [], children: [],
properties: { design: { ...all } },
}, },
{ {
_component: "@budibase/standard-components/login",
name: "Login", name: "Login",
description: description:
"A component that automatically generates a login screen for your app.", "A component that automatically generates a login screen for your app.",
icon: "ri-login-box-fill", icon: "ri-login-box-fill",
commonProps: {},
children: [], children: [],
properties: { design: { ...all } },
}, },
{ {
name: "Navigation Bar", name: "Navigation Bar",
@ -142,8 +254,8 @@ export default {
description: description:
"A component for handling the navigation within your app.", "A component for handling the navigation within your app.",
icon: "ri-navigation-fill", icon: "ri-navigation-fill",
commonProps: {},
children: [], children: [],
properties: { design: { ...all } },
}, },
], ],
}, },
@ -153,19 +265,17 @@ export default {
children: [ children: [
{ {
name: "Table", name: "Table",
_component: "@budibase/materialdesign-components/Datatable",
description: "A component that generates a table from your data.", description: "A component that generates a table from your data.",
icon: "ri-archive-drawer-fill", icon: "ri-archive-drawer-fill",
commonProps: {}, properties: { design: { ...all } },
children: [], children: [],
}, },
{ {
_component: "@budibase/materialdesign-components/Form",
name: "Form", name: "Form",
description: "A component that generates a form from your data.", description: "A component that generates a form from your data.",
icon: "ri-file-edit-fill", icon: "ri-file-edit-fill",
commonProps: {}, properties: { design: { ...all } },
component: "@budibase/materialdesign-components/Form", _component: "@budibase/materialdesign-components/Form",
template: { template: {
component: "@budibase/materialdesign-components/Form", component: "@budibase/materialdesign-components/Form",
description: "Form for saving a record", description: "Form for saving a record",
@ -178,7 +288,7 @@ export default {
name: "DataTable", name: "DataTable",
description: "A table for displaying data from the backend.", description: "A table for displaying data from the backend.",
icon: "ri-archive-drawer-fill", icon: "ri-archive-drawer-fill",
commonProps: {}, properties: { design: { ...all } },
children: [], children: [],
}, },
{ {
@ -186,7 +296,7 @@ export default {
name: "DataForm", name: "DataForm",
description: "Form stuff", description: "Form stuff",
icon: "ri-file-edit-fill", icon: "ri-file-edit-fill",
commonProps: {}, properties: { design: { ...all } },
children: [], children: [],
}, },
{ {
@ -194,7 +304,7 @@ export default {
_component: "@budibase/standard-components/datachart", _component: "@budibase/standard-components/datachart",
description: "Shiny chart", description: "Shiny chart",
icon: "ri-bar-chart-line", icon: "ri-bar-chart-line",
commonProps: {}, properties: { design: { ...all } },
children: [], children: [],
}, },
{ {
@ -202,7 +312,7 @@ export default {
_component: "@budibase/standard-components/datalist", _component: "@budibase/standard-components/datalist",
description: "Shiny list", description: "Shiny list",
icon: "ri-file-list-line", icon: "ri-file-list-line",
commonProps: {}, properties: { design: { ...all } },
children: [], children: [],
}, },
{ {
@ -210,7 +320,7 @@ export default {
_component: "@budibase/standard-components/datamap", _component: "@budibase/standard-components/datamap",
description: "Shiny map", description: "Shiny map",
icon: "ri-map-pin-line", icon: "ri-map-pin-line",
commonProps: {}, properties: { design: { ...all } },
children: [], children: [],
}, },
], ],

14
packages/builder/src/pages/[application]/backend/_layout.svelte

@ -38,12 +38,12 @@
height: 100%; height: 100%;
} }
@media only screen and (min-width: 1800px) { @media only screen and (min-width: 1800px) {
.nav { .nav {
overflow: auto; overflow: auto;
flex: 0 1 auto; flex: 0 1 auto;
width: 300px; width: 300px;
height: 100%; height: 100%;
}
} }
}
</style> </style>

325
packages/builder/tests/generate_css.spec.js

@ -1,320 +1,55 @@
import { import {
generate_css, generate_css,
make_margin,
generate_screen_css, generate_screen_css,
generate_array_styles
} from "../src/builderStore/generate_css.js" } from "../src/builderStore/generate_css.js"
describe("make_margin", () => {
test("it should generate a valid rule", () => {
expect(make_margin(["1", "1", "1", "1"])).toEqual("1px 1px 1px 1px")
})
test("empty values should output 0", () => {
expect(make_margin(["1", "1", "", ""])).toEqual("1px 1px 0px 0px")
expect(make_margin(["1", "", "", "1"])).toEqual("1px 0px 0px 1px")
expect(make_margin(["", "", "", ""])).toEqual("0px 0px 0px 0px")
})
})
describe("generate_css", () => { describe("generate_css", () => {
test("it should generate a valid css rule: grid-area", () => {
expect(generate_css({ layout: { gridarea: ["", "", "", ""] } })).toEqual({
layout: "",
position: "",
})
})
test("it should generate a valid css rule: grid-gap", () => {
expect(generate_css({ layout: { gap: "10" } })).toEqual({
layout: "grid-gap: 10px;\ndisplay: grid;",
position: "",
})
})
test("it should generate a valid css rule: column 1", () => {
expect(generate_css({ position: { column: ["", ""] } })).toEqual({
layout: "",
position: "",
})
})
test("it should generate a valid css rule: column 2", () => {
expect(generate_css({ position: { column: ["1", ""] } })).toEqual({
position: "grid-column-start: 1;",
layout: "",
})
})
test("it should generate a valid css rule: column 3", () => {
expect(generate_css({ position: { column: ["", "1"] } })).toEqual({
position: "grid-column-end: 1;",
layout: "",
})
})
test("it should generate a valid css rule: column 4", () => {
expect(generate_css({ position: { column: ["1", "1"] } })).toEqual({
position: "grid-column-start: 1;\ngrid-column-end: 1;",
layout: "",
})
})
test("it should generate a valid css rule: row 1", () => {
expect(generate_css({ position: { row: ["", ""] } })).toEqual({
layout: "",
position: "",
})
})
test("it should generate a valid css rule: row 2", () => {
expect(generate_css({ position: { row: ["1", ""] } })).toEqual({
position: "grid-row-start: 1;",
layout: "",
})
})
test("it should generate a valid css rule: row 3", () => {
expect(generate_css({ position: { row: ["", "1"] } })).toEqual({
position: "grid-row-end: 1;",
layout: "",
})
})
test("it should generate a valid css rule: row 4", () => {
expect(generate_css({ position: { row: ["1", "1"] } })).toEqual({
position: "grid-row-start: 1;\ngrid-row-end: 1;",
layout: "",
})
})
test("it should generate a valid css rule: padding 1", () => { test("Check how partially empty arrays are handled", () => {
expect( expect(["", "5", "", ""].map(generate_array_styles)).toEqual(["0px", "5px", "0px", "0px"])
generate_css({ position: { padding: ["1", "1", "1", "1"] } })
).toEqual({
position: "padding: 1px 1px 1px 1px;",
layout: "",
})
}) })
test("it should generate a valid css rule: padding 2", () => { test("Check how array styles are output", () => {
expect(generate_css({ position: { padding: ["1", "", "", "1"] } })).toEqual( expect(generate_css({ margin: ["0", "10", "0", "15"] })).toBe("margin: 0px 10px 0px 15px;")
{
position: "padding: 1px 0px 0px 1px;",
layout: "",
}
)
}) })
test("it should generate a valid css rule: margin 1", () => { test("Check handling of an array with empty string values", () => {
expect( expect(generate_css({ padding: ["", "", "", ""] })).toBe("")
generate_css({ position: { margin: ["1", "1", "1", "1"] } })
).toEqual({
position: "margin: 1px 1px 1px 1px;",
layout: "",
})
}) })
test("it should generate a valid css rule: margin 2", () => { test("Check handling of an empty array", () => {
expect(generate_css({ position: { margin: ["1", "", "", "1"] } })).toEqual({ expect(generate_css({ margin: [] })).toBe("")
position: "margin: 1px 0px 0px 1px;",
layout: "",
})
}) })
test("it should generate a valid css rule: z-index 1", () => { test("Check handling of valid font property", () => {
expect(generate_css({ position: { zindex: "" } })).toEqual({ expect(generate_css({ "font-size": "10px" })).toBe("font-size: 10px;")
position: "",
layout: "",
})
})
test("it should generate a valid css rule: z-index 2", () => {
expect(generate_css({ position: { zindex: "1" } })).toEqual({
position: "z-index: 1;",
layout: "",
})
}) })
}) })
describe("generate_screen_css", () => {
test("it should compile the css for a list of components", () => {
const components = [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 1,
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 2,
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 3,
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 4,
},
]
const compiled = `.pos-1 {
margin: 1px 1px 1px 1px;
}
.lay-1 {
}
.pos-2 {
margin: 1px 1px 1px 1px;
}
.lay-2 {
}
.pos-3 {
margin: 1px 1px 1px 1px;
}
.lay-3 {
}
.pos-4 {
margin: 1px 1px 1px 1px;
}
.lay-4 {
}`
expect(generate_screen_css(components)).toEqual(compiled) describe("generate_screen_css", () => {
const normalComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: { "font-size": "16px" }, hover: {}, active: {}, selected: {} } }
test("Test generation of normal css styles", () => {
expect(generate_screen_css([normalComponent])).toBe(".header-123-456 {\nfont-size: 16px;\n}")
}) })
test("it should compile the css for a list of components", () => { const hoverComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {"font-size": "16px"}, active: {}, selected: {} } }
const components = [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 1,
_children: [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 2,
_children: [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 3,
_children: [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 4,
_children: [
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 5,
_children: [],
},
],
},
],
},
],
},
],
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 6,
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 7,
},
{
_styles: {
layout: { gridarea: ["", "", "", ""] },
position: { margin: ["1", "1", "1", "1"] },
},
_id: 8,
},
]
const compiled = `.pos-1 {
margin: 1px 1px 1px 1px;
}
.lay-1 {
}
.pos-2 {
margin: 1px 1px 1px 1px;
}
.lay-2 {
}
.pos-3 {
margin: 1px 1px 1px 1px;
}
.lay-3 {
}
.pos-4 {
margin: 1px 1px 1px 1px;
}
.lay-4 {
} test("Test generation of hover css styles", () => {
.pos-5 { expect(generate_screen_css([hoverComponent])).toBe(".header-123-456:hover {\nfont-size: 16px;\n}")
margin: 1px 1px 1px 1px; })
}
.lay-5 {
}
.pos-6 {
margin: 1px 1px 1px 1px;
}
.lay-6 {
}
.pos-7 {
margin: 1px 1px 1px 1px;
}
.lay-7 {
} const selectedComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: { "font-size": "16px" } } }
.pos-8 {
margin: 1px 1px 1px 1px; test("Test generation of selection css styles", () => {
} expect(generate_screen_css([selectedComponent])).toBe(".header-123-456::selection {\nfont-size: 16px;\n}")
.lay-8 { })
}` const emptyComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: {} } }
expect(generate_screen_css(components)).toEqual(compiled) test.only("Testing handling of empty component styles", () => {
}) expect(generate_screen_css([emptyComponent])).toBe("")
}) })
})

6
packages/cli/src/commands/new/appDirectoryTemplate/pages/main/page.json

@ -9,8 +9,10 @@
"_id": 0, "_id": 0,
"type": "div", "type": "div",
"_styles": { "_styles": {
"layout": {}, "normal": {},
"position": {} "hover": {},
"active": {},
"selected": {}
}, },
"_code": "" "_code": ""
}, },

6
packages/cli/src/commands/new/appDirectoryTemplate/pages/unauthenticated/page.json

@ -9,8 +9,10 @@
"_id": 1, "_id": 1,
"type": "div", "type": "div",
"_styles": { "_styles": {
"layout": {}, "normal": {},
"position": {} "hover": {},
"active": {},
"selected": {}
}, },
"_code": "" "_code": ""
}, },

6
packages/cli/src/commands/new/page.template.json

@ -9,8 +9,10 @@
"_id": 0, "_id": 0,
"type": "div", "type": "div",
"_styles": { "_styles": {
"layout": {}, "normal": {},
"position": {} "hover": {},
"active": {},
"selected": {}
}, },
"_code": "" "_code": ""
}, },

2
packages/client/src/render/attachChildren.js

@ -31,7 +31,7 @@ export const attachChildren = initialiseOpts => (htmlElement, options) => {
} }
} }
htmlElement.classList.add(`lay-${treeNode.props._id}`) // htmlElement.classList.add(`lay-${treeNode.props._id}`)
const childNodes = [] const childNodes = []
for (let childProps of treeNode.props._children) { for (let childProps of treeNode.props._children) {

3
packages/client/src/render/prepareRenderComponent.js

@ -35,8 +35,9 @@ export const prepareRenderComponent = ({
thisNode.rootElement = thisNode.rootElement =
htmlElement.children[htmlElement.children.length - 1] htmlElement.children[htmlElement.children.length - 1]
let [componentName] = props._component.match(/[a-z]*$/)
if (props._id && thisNode.rootElement) { if (props._id && thisNode.rootElement) {
thisNode.rootElement.classList.add(`pos-${props._id}`) thisNode.rootElement.classList.add(`${componentName}-${props._id}`)
} }
} }
} }

132
packages/standard-components/components.json

@ -12,10 +12,10 @@
"props": { "props": {
"logoUrl": "string", "logoUrl": "string",
"title": "string", "title": "string",
"backgroundColor": "colour", "backgroundColor": "string",
"color": "colour", "color": "string",
"borderWidth": "string", "borderWidth": "string",
"borderColor": "colour", "borderColor": "string",
"borderStyle": "string" "borderStyle": "string"
} }
}, },
@ -23,35 +23,10 @@
"name": "Button", "name": "Button",
"description": "an html <button />", "description": "an html <button />",
"props": { "props": {
"contentText": { "text": "string",
"type": "string",
"default": "Button"
},
"className": "string", "className": "string",
"disabled": "bool", "disabled": "bool",
"onClick": "event", "onClick": "event"
"background": "colour",
"color": "colour",
"border": "string",
"padding": "string",
"hoverColor": "string",
"hoverBackground": "string",
"hoverBorder": "string",
"fontFamily": {
"type": "options",
"default": "initial",
"styleBindingProperty": "font-family",
"options": [
"initial",
"Times New Roman",
"Georgia",
"Arial",
"Arial Black",
"Comic Sans MS",
"Impact",
"Lucida Sans Unicode"
]
}
}, },
"tags": [ "tags": [
"layout" "layout"
@ -167,58 +142,7 @@
"children": false, "children": false,
"props": { "props": {
"text": "string", "text": "string",
"color": "colour", "type": {"type": "string", "default": "none"}
"fontFamily": {
"type": "options",
"default": "initial",
"styleBindingProperty": "font-family",
"options": [
"initial",
"Times New Roman",
"Georgia",
"Arial",
"Arial Black",
"Comic Sans MS",
"Impact",
"Lucida Sans Unicode"
]
},
"fontSize": "string",
"textAlign": {
"type": "options",
"default": "inline",
"options": [
"left",
"center",
"right"
]
},
"verticalAlign": {
"type": "options",
"default": "inline",
"options": [
"top",
"middle",
"bottom"
]
},
"formattingTag": {
"type": "options",
"default": "none",
"options": [
"none",
"<b> - bold",
"<strong> - important",
"<i> - italic",
"<em> - emphasized",
"<mark> - marked text",
"<small> - small",
"<del> - deleted",
"<ins> - inserted",
"<sub> - subscript",
"<sup> - superscript"
]
}
}, },
"tags": [ "tags": [
"div", "div",
@ -230,6 +154,7 @@
"description": "A component that allows the user to input text.", "description": "A component that allows the user to input text.",
"props": { "props": {
"label": "string", "label": "string",
"type": "string",
"value": "string", "value": "string",
"onchange": "event" "onchange": "event"
} }
@ -259,7 +184,7 @@
"props": { "props": {
"icon": "string", "icon": "string",
"fontSize": "string", "fontSize": "string",
"color": "colour" "color": "string"
} }
}, },
"datatable": { "datatable": {
@ -328,8 +253,8 @@
"url": "string", "url": "string",
"openInNewTab": "bool", "openInNewTab": "bool",
"text": "string", "text": "string",
"color": "colour", "color": "string",
"hoverColor": "colour", "hoverColor": "string",
"underline": "bool", "underline": "bool",
"fontSize": "string", "fontSize": "string",
"fontFamily": { "fontFamily": {
@ -383,27 +308,8 @@
"summary" "summary"
], ],
"default": "div" "default": "div"
},
"backgroundColor": "string",
"color": "string",
"borderWidth": "string",
"borderColor": "string",
"borderStyle": {
"type": "options",
"options": [
"none",
"solid",
"dotted",
"dashed",
"double",
"groove",
"ridge",
"inset",
"outset"
],
"default": "none"
} }
}, },
"container": true, "container": true,
"tags": [ "tags": [
"div", "div",
@ -416,7 +322,6 @@
"description": "An HTML H1 - H6 tag", "description": "An HTML H1 - H6 tag",
"props": { "props": {
"className": "string", "className": "string",
"color":"colour",
"text": "string", "text": "string",
"type": { "type": {
"type": "options", "type": "options",
@ -429,21 +334,6 @@
"h5", "h5",
"h6" "h6"
] ]
},
"fontFamily": {
"type": "options",
"default": "initial",
"styleBindingProperty": "font-family",
"options": [
"initial",
"Times New Roman",
"Georgia",
"Arial",
"Arial Black",
"Comic Sans MS",
"Impact",
"Lucida Sans Unicode"
]
} }
}, },
"tags": [] "tags": []

56
packages/standard-components/src/Button.svelte

@ -1,58 +1,15 @@
<script> <script>
import { cssVars, createClasses } from "./cssVars"
import { buildStyle } from "./buildStyle"
export let className = "default" export let className = "default"
export let disabled = false export let disabled = false
export let contentText export let text
export let onClick export let onClick
export let background
export let color
export let border
export let padding
export let hoverColor
export let hoverBackground
export let hoverBorder
export let fontFamily
export let _bb export let _bb
let theButton let theButton
let cssVariables
let buttonStyles
let customHoverColorClass
let customHoverBorderClass
let customHoverBackClass
let customClasses = ""
$: if (_bb.props._children && _bb.props._children.length > 0) $: if (_bb.props._children && _bb.props._children.length > 0)
theButton && _bb.attachChildren(theButton) theButton && _bb.attachChildren(theButton)
$: {
cssVariables = {
hoverColor,
hoverBorder,
hoverBackground,
background,
color,
border,
}
buttonStyles = buildStyle({
padding,
"font-family": fontFamily,
})
customClasses = createClasses({
hoverColor,
hoverBorder,
hoverBackground,
background,
border,
color,
})
}
const clickHandler = () => { const clickHandler = () => {
_bb.call(onClick) _bb.call(onClick)
} }
@ -60,15 +17,10 @@
<button <button
bind:this={theButton} bind:this={theButton}
use:cssVars={cssVariables} class={className}
class="{className}
{customClasses}"
disabled={disabled || false} disabled={disabled || false}
on:click={clickHandler} on:click={clickHandler}>
style={buttonStyles}> {#if !_bb.props._children || _bb.props._children.length === 0}{text}{/if}
{#if !_bb.props._children || _bb.props._children.length === 0}
{contentText}
{/if}
</button> </button>
<style> <style>

98
packages/standard-components/src/Container.svelte

@ -4,26 +4,12 @@
export let className = "" export let className = ""
export let onLoad export let onLoad
export let type = "div" export let type = "div"
export let backgroundColor
export let color
export let borderWidth
export let borderColor
export let borderStyle
export let _bb export let _bb
let containerElement let containerElement
let hasLoaded let hasLoaded
let currentChildren let currentChildren
$: cssVariables = {
backgroundColor,
color,
borderWidth,
borderColor,
borderStyle,
}
$: classes = `${createClasses(cssVariables)} ${className}`
$: { $: {
if (containerElement) { if (containerElement) {
_bb.attachChildren(containerElement) _bb.attachChildren(containerElement)
@ -36,87 +22,29 @@
</script> </script>
{#if type === 'div'} {#if type === 'div'}
<div <div bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'header'} {:else if type === 'header'}
<header <header bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'main'} {:else if type === 'main'}
<main <main bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'footer'} {:else if type === 'footer'}
<footer <footer bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'aside'} {:else if type === 'aside'}
<aside <aside bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'summary'} {:else if type === 'summary'}
<summary <summary bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'details'} {:else if type === 'details'}
<details <details bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'article'} {:else if type === 'article'}
<article <article bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'nav'} {:else if type === 'nav'}
<nav <nav bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'mark'} {:else if type === 'mark'}
<mark <mark bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'figure'} {:else if type === 'figure'}
<figure <figure bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'figcaption'} {:else if type === 'figcaption'}
<figcaption <figcaption bind:this={containerElement} />
class={classes}
bind:this={containerElement}
use:cssVars={cssVariables} />
{:else if type === 'paragraph'} {:else if type === 'paragraph'}
<p class={classes} bind:this={containerElement} use:cssVars={cssVariables} /> <p bind:this={containerElement} />
{/if} {/if}
<style>
.backgroundColor {
background-color: var(--backgroundColor);
}
.color {
color: var(--color);
}
.borderColor {
border-color: var(--borderColor);
}
.borderWidth {
border-width: var(--borderWidth);
}
.borderStyle {
border-style: var(--borderStyle);
}
</style>

19
packages/standard-components/src/Heading.svelte

@ -2,28 +2,25 @@
import { buildStyle } from "./buildStyle.js" import { buildStyle } from "./buildStyle.js"
export let className = "" export let className = ""
export let type export let type
export let _bb
export let text = "" export let text = ""
export let fontFamily = ""
export let color = "" export let _bb
let containerElement let containerElement
$: containerElement && !text && _bb.attachChildren(containerElement) $: containerElement && !text && _bb.attachChildren(containerElement)
$: style = buildStyle({ "font-family": fontFamily, color })
// $: console.log("HEADING", color)
</script> </script>
{#if type === 'h1'} {#if type === 'h1'}
<h1 class={className} {style} bind:this={containerElement}>{text}</h1> <h1 class={className} bind:this={containerElement}>{text}</h1>
{:else if type === 'h2'} {:else if type === 'h2'}
<h2 class={className} {style} bind:this={containerElement}>{text}</h2> <h2 class={className} bind:this={containerElement}>{text}</h2>
{:else if type === 'h3'} {:else if type === 'h3'}
<h3 class={className} {style} bind:this={containerElement}>{text}</h3> <h3 class={className} bind:this={containerElement}>{text}</h3>
{:else if type === 'h4'} {:else if type === 'h4'}
<h4 class={className} {style} bind:this={containerElement}>{text}</h4> <h4 class={className} bind:this={containerElement}>{text}</h4>
{:else if type === 'h5'} {:else if type === 'h5'}
<h5 class={className} {style} bind:this={containerElement}>{text}</h5> <h5 class={className} bind:this={containerElement}>{text}</h5>
{:else if type === 'h6'} {:else if type === 'h6'}
<h6 class={className} {style} bind:this={containerElement}>{text}</h6> <h6 class={className} bind:this={containerElement}>{text}</h6>
{/if} {/if}

38
packages/standard-components/src/Link.svelte

@ -4,11 +4,6 @@
export let url = "" export let url = ""
export let text = "" export let text = ""
export let openInNewTab = false export let openInNewTab = false
export let color
export let hoverColor
export let underline = false
export let fontFamily
export let fontSize
export let _bb export let _bb
@ -16,43 +11,12 @@
$: anchorElement && !text && _bb.attachChildren(anchorElement) $: anchorElement && !text && _bb.attachChildren(anchorElement)
$: target = openInNewTab ? "_blank" : "_self" $: target = openInNewTab ? "_blank" : "_self"
$: cssVariables = {
hoverColor,
color,
textDecoration: underline ? "underline" : "none",
fontSize,
fontFamily,
}
$: classes = createClasses(cssVariables)
</script> </script>
<a <a href={url} bind:this={anchorElement} {target}>{text}</a>
href={url}
bind:this={anchorElement}
class={classes}
{target}
use:cssVars={cssVariables}>
{text}
</a>
<style> <style>
.color {
color: var(--color);
}
.hoverColor:hover {
color: var(--color);
}
.textDecoration { .textDecoration {
text-decoration: var(--textDecoration); text-decoration: var(--textDecoration);
} }
.fontSize {
font-size: var(--fontSize);
}
.fontFamily {
font-family: var(--fontFamily);
}
</style> </style>

62
packages/standard-components/src/Text.svelte

@ -4,45 +4,35 @@
export let text = "" export let text = ""
export let className = "" export let className = ""
export let formattingTag = "" export let type = ""
export let fontFamily = ""
export let fontSize = "1em"
export let textAlign = ""
export let verticalAlign = ""
export let color = ""
export let _bb export let _bb
const isTag = tag => (formattingTag || "").indexOf(tag) > -1 const isTag = tag => type === tag
$: style = buildStyle({
"font-size": fontSize,
"font-family": fontFamily,
color,
})
</script> </script>
{#if isTag('none')} {#if isTag('none')}
<span {style}>{text}</span> <span>{text}</span>
{:else if isTag('<b>')} {:else if isTag('bold')}
<b class={className} {style}>{text}</b> <b class={className}>{text}</b>
{:else if isTag('<strong>')} {:else if isTag('strong')}
<strong class={className} {style}>{text}</strong> <strong class={className}>{text}</strong>
{:else if isTag('<i>')} {:else if isTag('italic')}
<i class={className} {style}>{text}</i> <i class={className}>{text}</i>
{:else if isTag('<em>')} {:else if isTag('emphasis')}
<em class={className} {style}>{text}</em> <em class={className}>{text}</em>
{:else if isTag('<mark>')} {:else if isTag('mark')}
<mark class={className} {style}>{text}</mark> <mark class={className}>{text}</mark>
{:else if isTag('<small>')} {:else if isTag('small')}
<small class={className} {style}>{text}</small> <small class={className}>{text}</small>
{:else if isTag('<del>')} {:else if isTag('del')}
<del class={className} {style}>{text}</del> <del class={className}>{text}</del>
{:else if isTag('<ins>')} {:else if isTag('ins')}
<ins class={className} {style}>{text}</ins> <ins class={className}>{text}</ins>
{:else if isTag('<sub>')} {:else if isTag('sub')}
<sub class={className} {style}>{text}</sub> <sub class={className}>{text}</sub>
{:else if isTag('<sup>')} {:else if isTag('sup')}
<sup class={className} {style}>{text}</sup> <sup class={className}>{text}</sup>
{:else}{text}{/if} {:else}
<span>{text}</span>
{/if}

Loading…
Cancel
Save