Merge pull request #253 from Budibase/property-panel/master
Complete CSS State Styles and Property Panel Structure
This commit is contained in:
commit
b4c1468889
|
@ -79,7 +79,8 @@
|
|||
"rollup-plugin-svelte": "^5.0.3",
|
||||
"rollup-plugin-terser": "^4.0.4",
|
||||
"rollup-plugin-url": "^2.2.2",
|
||||
"svelte": "^3.0.0"
|
||||
"svelte": "^3.0.0",
|
||||
"svelte-color-picker": "^1.0.7"
|
||||
},
|
||||
"gitHead": "115189f72a850bfb52b65ec61d932531bf327072"
|
||||
}
|
|
@ -1,142 +1,54 @@
|
|||
import { pipe } from "components/common/core"
|
||||
import { filter, map, reduce, toPairs } from "lodash/fp"
|
||||
|
||||
const self = n => n
|
||||
const join_with = delimiter => a => a.join(delimiter)
|
||||
const empty_string_to_unset = s => (s.length ? s : "0")
|
||||
const add_suffix_if_number = suffix => s => {
|
||||
try {
|
||||
if (isNaN(s) || isNaN(parseFloat(s))) return s
|
||||
} catch (_) {
|
||||
return s
|
||||
}
|
||||
return s + suffix
|
||||
}
|
||||
|
||||
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]) =>
|
||||
`${css_map[name].name}: ${css_map[name].generate(values)};`
|
||||
|
||||
const handle_grid = (acc, [name, value]) => {
|
||||
let tmp = []
|
||||
|
||||
if (name === "row" || name === "column") {
|
||||
if (value[0]) tmp.push([`${name}start`, value[0]])
|
||||
if (value[1]) tmp.push([`${name}end`, value[1]])
|
||||
return acc.concat(tmp)
|
||||
}
|
||||
|
||||
return acc.concat([[name, value]])
|
||||
}
|
||||
|
||||
const object_to_css_string = [
|
||||
toPairs,
|
||||
reduce(handle_grid, []),
|
||||
filter(v => (Array.isArray(v[1]) ? v[1].some(s => s.length) : v[1].length)),
|
||||
map(generate_rule),
|
||||
join_with("\n"),
|
||||
]
|
||||
|
||||
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 generate_screen_css = component_array => {
|
||||
export const generate_screen_css = component_arr => {
|
||||
let styles = ""
|
||||
let emptyStyles = { layout: {}, position: {} }
|
||||
|
||||
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"
|
||||
for (const { _styles, _id, _children, _component } of component_arr) {
|
||||
let [componentName] = _component.match(/[a-z]*$/)
|
||||
Object.keys(_styles).forEach(selector => {
|
||||
const cssString = generate_css(_styles[selector])
|
||||
if (cssString) {
|
||||
styles += apply_class(_id, componentName, cssString, selector)
|
||||
}
|
||||
})
|
||||
if (_children && _children.length) {
|
||||
styles += generate_screen_css(_children) + "\n"
|
||||
}
|
||||
}
|
||||
return styles.trim()
|
||||
}
|
||||
|
||||
export const generate_css = style => {
|
||||
let cssString = Object.entries(style).reduce((str, [key, value]) => {
|
||||
//TODO Handle arrays and objects here also
|
||||
if (typeof value === "string") {
|
||||
if (value) {
|
||||
return (str += `${key}: ${value};\n`)
|
||||
}
|
||||
} else if (Array.isArray(value)) {
|
||||
if (value.length > 0 && !value.every(v => v === "")) {
|
||||
return (str += `${key}: ${value
|
||||
.map(generate_array_styles)
|
||||
.join(" ")};\n`)
|
||||
}
|
||||
}
|
||||
}, "")
|
||||
|
||||
return (cssString || "").trim()
|
||||
}
|
||||
|
||||
export const generate_array_styles = item => {
|
||||
let safeItem = item === "" ? 0 : item
|
||||
let hasPx = new RegExp("px$")
|
||||
if (!hasPx.test(safeItem)) {
|
||||
return `${safeItem}px`
|
||||
} else {
|
||||
return safeItem
|
||||
}
|
||||
}
|
||||
|
||||
export const apply_class = (id, name = "element", styles, selector) => {
|
||||
if (selector === "normal") {
|
||||
return `.${name}-${id} {\n${styles}\n}`
|
||||
} else {
|
||||
let sel = selector === "selected" ? "::selection" : `:${selector}`
|
||||
return `.${name}-${id}${sel} {\n${styles}\n}`
|
||||
}
|
||||
}
|
||||
|
|
|
@ -140,10 +140,11 @@ const _saveScreen = async (store, s, screen) => {
|
|||
return s
|
||||
}
|
||||
|
||||
const _saveScreenApi = (screen, s) =>
|
||||
const _saveScreenApi = (screen, s) => {
|
||||
api
|
||||
.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}/screen`, screen)
|
||||
.then(() => _savePage(s))
|
||||
}
|
||||
|
||||
const createScreen = store => (screenName, route, layoutComponentName) => {
|
||||
store.update(state => {
|
||||
|
@ -278,7 +279,6 @@ const removeStylesheet = store => stylesheet => {
|
|||
|
||||
const _savePage = async s => {
|
||||
const page = s.pages[s.currentPageName]
|
||||
|
||||
await api.post(`/_builder/api/${s.appId}/pages/${s.currentPageName}`, {
|
||||
page: { componentLibraries: s.pages.componentLibraries, ...page },
|
||||
uiFunctions: s.currentPageFunctions,
|
||||
|
@ -427,6 +427,7 @@ const setComponentStyle = store => (type, name, value) => {
|
|||
state.currentComponentInfo._styles = {}
|
||||
}
|
||||
state.currentComponentInfo._styles[type][name] = value
|
||||
|
||||
state.currentPreviewItem._css = generate_screen_css([
|
||||
state.currentPreviewItem.props,
|
||||
])
|
||||
|
|
|
@ -1,13 +1,78 @@
|
|||
<script>
|
||||
import { onMount, beforeUpdate, afterUpdate } from "svelte"
|
||||
import { onMount } from "svelte"
|
||||
import { HsvPicker } from "svelte-color-picker"
|
||||
|
||||
export let value = null
|
||||
export let onChanged = () => {}
|
||||
export let swatches = []
|
||||
// export let initialValue = "#ffffff"
|
||||
export let onChange = color => {}
|
||||
export let open = false
|
||||
let value = "#ffffff"
|
||||
|
||||
let picker
|
||||
let cp = null
|
||||
let _justMounted = true //see onColorChange
|
||||
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() {
|
||||
let colorStore = localStorage.getItem("bb:recentColors")
|
||||
if (!!colorStore) {
|
||||
|
@ -25,47 +90,27 @@
|
|||
picker.addSwatch(color)
|
||||
localStorage.setItem("bb:recentColors", JSON.stringify(swatches))
|
||||
}
|
||||
} -->
|
||||
|
||||
<style>
|
||||
.overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* background: rgba(5, 5, 5, 0.25); */
|
||||
}
|
||||
|
||||
function createPicker() {
|
||||
picker = Pickr.create({
|
||||
el: cp,
|
||||
theme: "nano",
|
||||
default: value || "#000000",
|
||||
|
||||
swatches,
|
||||
closeWithKey: "Escape",
|
||||
|
||||
components: {
|
||||
preview: true,
|
||||
opacity: true,
|
||||
hue: true,
|
||||
|
||||
interaction: {
|
||||
hex: true,
|
||||
rgba: true,
|
||||
input: true,
|
||||
save: true,
|
||||
},
|
||||
},
|
||||
})
|
||||
.cp {
|
||||
position: absolute;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
afterUpdate(() => {
|
||||
picker.setColor(value)
|
||||
})
|
||||
|
||||
onMount(() => {
|
||||
getRecentColors()
|
||||
createPicker()
|
||||
|
||||
picker.on("save", (colour, instance) => {
|
||||
let color = colour.toHEXA().toString()
|
||||
onChanged(color)
|
||||
setRecentColor(color)
|
||||
picker.hide()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<div bind:this={cp} class="color-picker" />
|
||||
.color-preview {
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
margin: 5px;
|
||||
cursor: pointer;
|
||||
border: 1px solid gainsboro;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
<script>
|
||||
export let value = ""
|
||||
export let width = ""
|
||||
|
||||
let style = { width }
|
||||
</script>
|
||||
|
||||
<input type="text" on:change bind:value />
|
||||
<input type="text" style={`width: ${width};`} on:change bind:value />
|
||||
|
||||
<style>
|
||||
input {
|
||||
|
|
|
@ -2,36 +2,50 @@
|
|||
import { onMount } from "svelte"
|
||||
|
||||
export let meta = []
|
||||
export let size = ""
|
||||
export let values = []
|
||||
export let label = ""
|
||||
export let value = [0, 0, 0, 0]
|
||||
export let type = "number"
|
||||
export let onStyleChanged = () => {}
|
||||
export let onChange = () => {}
|
||||
|
||||
let _values = values.map(v => v)
|
||||
|
||||
$: onStyleChanged(_values)
|
||||
function handleChange(val, idx) {
|
||||
value.splice(idx, 1, val)
|
||||
value = value
|
||||
onChange(value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="inputs {size}">
|
||||
{#each meta as { placeholder }, i}
|
||||
<input
|
||||
{type}
|
||||
{placeholder}
|
||||
value={values[i]}
|
||||
on:input={e => (_values[i] = e.target.value)} />
|
||||
{/each}
|
||||
<div class="input-container">
|
||||
<div class="label">{label}</div>
|
||||
<div class="inputs">
|
||||
{#each meta as { placeholder }, i}
|
||||
<input
|
||||
{type}
|
||||
placeholder={placeholder || ''}
|
||||
value={!value || value[i] === 0 ? '' : value[i]}
|
||||
on:change={e => handleChange(e.target.value || 0, i)} />
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.inputs {
|
||||
.input-container {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.label {
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 83px;
|
||||
width: 40px;
|
||||
height: 32px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
margin: 0px 5px;
|
||||
color: #163057;
|
||||
opacity: 0.7;
|
||||
padding: 5px 10px;
|
||||
|
@ -49,17 +63,11 @@
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.small > input {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding: 0;
|
||||
input[type="number"] {
|
||||
-moz-appearance: textfield;
|
||||
}
|
||||
|
||||
.small > input::placeholder {
|
||||
input::placeholder {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -16,6 +16,11 @@
|
|||
return props
|
||||
}
|
||||
|
||||
const getComponentTypeName = component => {
|
||||
let [componentName] = component._component.match(/[a-z]*$/)
|
||||
return componentName || "element"
|
||||
}
|
||||
|
||||
$: iframe &&
|
||||
console.log(
|
||||
iframe.contentDocument.head.insertAdjacentHTML(
|
||||
|
@ -60,7 +65,7 @@
|
|||
_children: [
|
||||
{
|
||||
_component: "@budibase/standard-components/container",
|
||||
_styles: { position: {}, layout: {} },
|
||||
_styles: { normal: {}, hover: {}, active: {}, selected: {} },
|
||||
_id: "__screenslot__text",
|
||||
_code: "",
|
||||
className: "",
|
||||
|
@ -69,7 +74,12 @@
|
|||
_children: [
|
||||
{
|
||||
_component: "@budibase/standard-components/text",
|
||||
_styles: { position: {}, layout: {} },
|
||||
_styles: {
|
||||
normal: {},
|
||||
hover: {},
|
||||
active: {},
|
||||
selected: {},
|
||||
},
|
||||
_id: "__screenslot__text_2",
|
||||
_code: "",
|
||||
text: "content",
|
||||
|
@ -88,6 +98,8 @@
|
|||
appRootPath: "",
|
||||
}
|
||||
|
||||
$: selectedComponentType = getComponentTypeName($store.currentComponentInfo)
|
||||
|
||||
$: selectedComponentId = $store.currentComponentInfo
|
||||
? $store.currentComponentInfo._id
|
||||
: ""
|
||||
|
@ -102,6 +114,7 @@
|
|||
srcdoc={iframeTemplate({
|
||||
styles,
|
||||
stylesheetLinks,
|
||||
selectedComponentType,
|
||||
selectedComponentId,
|
||||
frontendDefinition: JSON.stringify(frontendDefinition),
|
||||
currentPageFunctions: $store.currentPageFunctions,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
export default ({
|
||||
styles,
|
||||
stylesheetLinks,
|
||||
selectedComponentType,
|
||||
selectedComponentId,
|
||||
frontendDefinition,
|
||||
currentPageFunctions,
|
||||
|
@ -11,7 +12,7 @@ export default ({
|
|||
<style>
|
||||
${styles || ""}
|
||||
|
||||
.pos-${selectedComponentId} {
|
||||
.${selectedComponentType}-${selectedComponentId} {
|
||||
border: 2px solid #0055ff;
|
||||
}
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -1,4 +1,5 @@
|
|||
<script>
|
||||
import { setContext, onMount } from "svelte"
|
||||
import PropsView from "./PropsView.svelte"
|
||||
import { store } from "builderStore"
|
||||
import IconButton from "components/common/IconButton.svelte"
|
||||
|
@ -13,23 +14,63 @@
|
|||
import LayoutEditor from "./LayoutEditor.svelte"
|
||||
import EventsEditor from "./EventsEditor"
|
||||
|
||||
let current_view = "props"
|
||||
let codeEditor
|
||||
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 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
|
||||
$: 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 =
|
||||
$store.currentFrontEndType === "page"
|
||||
? getProps($store.currentPreviewItem, ["name", "favicon"])
|
||||
: getProps($store.currentPreviewItem, ["name", "description", "route"])
|
||||
|
||||
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) {
|
||||
return keys.map((key, i) => [key, obj[key], obj.props._id + i])
|
||||
|
@ -37,116 +78,33 @@
|
|||
</script>
|
||||
|
||||
<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>
|
||||
|
||||
<CategoryTab
|
||||
onClick={category => (selectedCategory = category)}
|
||||
{categories}
|
||||
{selectedCategory} />
|
||||
|
||||
<div class="component-props-container">
|
||||
|
||||
{#if current_view === 'props'}
|
||||
{#if $store.currentView === 'detail'}
|
||||
{#each screen_props as [k, v, id] (id)}
|
||||
<div class="detail-prop" for={k}>
|
||||
<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 selectedCategory.value === 'design'}
|
||||
<DesignView {panelDefinition} {componentInstance} {onStyleChanged} />
|
||||
{:else if selectedCategory.value === 'settings'}
|
||||
<SettingsView
|
||||
{componentInstance}
|
||||
{componentDefinition}
|
||||
{panelDefinition}
|
||||
onChange={onPropChanged} />
|
||||
{/if}
|
||||
|
||||
<CodeEditor
|
||||
bind:this={codeEditor}
|
||||
code={component._code}
|
||||
onCodeChanged={store.setComponentCode} />
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<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 {
|
||||
height: 100%;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.title > div:nth-child(1) {
|
||||
|
@ -162,52 +120,4 @@
|
|||
margin-top: 10px;
|
||||
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>
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { splitName } from "./pagesParsing/splitRootComponentName.js"
|
||||
import components from "./temporaryPanelStructure.js"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import CategoryTab from "./CategoryTab.svelte"
|
||||
import {
|
||||
find,
|
||||
sortBy,
|
||||
|
@ -36,15 +37,12 @@
|
|||
</script>
|
||||
|
||||
<div class="root">
|
||||
<ul class="tabs">
|
||||
{#each categories as category}
|
||||
<li
|
||||
on:click={() => (selectedCategory = category)}
|
||||
class:active={selectedCategory === category}>
|
||||
{category.name}
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
|
||||
<CategoryTab
|
||||
onClick={category => (selectedCategory = category)}
|
||||
{selectedCategory}
|
||||
{categories} />
|
||||
|
||||
<div class="panel">
|
||||
<Tab
|
||||
list={selectedCategory}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
<button
|
||||
class:selected={selected === PROPERTIES_TAB}
|
||||
on:click={() => selectTab(PROPERTIES_TAB)}>
|
||||
Properties
|
||||
Edit
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
@ -50,6 +50,14 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
.root {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px 0;
|
||||
border-left: solid 1px #e8e8ef;
|
||||
}
|
||||
|
||||
.switcher {
|
||||
display: flex;
|
||||
margin: 0px 20px 20px 20px;
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -24,7 +24,7 @@ export const createProps = (componentDefinition, derivedFromProps) => {
|
|||
const props = {
|
||||
_id: uuid(),
|
||||
_component: componentDefinition._component,
|
||||
_styles: { position: {}, layout: {} },
|
||||
_styles: { normal: {}, hover: {}, active: {}, selected: {} },
|
||||
_code: "",
|
||||
}
|
||||
|
||||
|
@ -71,7 +71,7 @@ export const makePropsSafe = (componentDefinition, props) => {
|
|||
}
|
||||
|
||||
if (!props._styles) {
|
||||
props._styles = { layout: {}, position: {} }
|
||||
props._styles = { normal: {}, hover: {}, active: {}, selected: {} }
|
||||
}
|
||||
|
||||
return props
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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 {
|
||||
categories: [
|
||||
{
|
||||
|
@ -20,6 +26,31 @@ export default {
|
|||
icon: "ri-layout-row-fill",
|
||||
commonProps: {},
|
||||
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",
|
||||
|
@ -32,13 +63,21 @@ export default {
|
|||
name: "Headline",
|
||||
description: "A component for displaying heading text",
|
||||
icon: "ri-heading",
|
||||
props: {
|
||||
type: {
|
||||
type: "options",
|
||||
options: ["h1", "h2", "h3", "h4", "h5", "h6"],
|
||||
default: "h1",
|
||||
},
|
||||
text: "string",
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [
|
||||
{
|
||||
key: "text",
|
||||
label: "Text",
|
||||
control: Input,
|
||||
},
|
||||
{
|
||||
key: "type",
|
||||
label: "Type",
|
||||
control: OptionSelect,
|
||||
options: ["h1", "h2", "h3", "h4", "h5", "h6"],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -46,7 +85,34 @@ export default {
|
|||
name: "Paragraph",
|
||||
description: "A component for displaying paragraph text.",
|
||||
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:
|
||||
"A textfield component that allows the user to input text.",
|
||||
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",
|
||||
name: "Checkbox",
|
||||
description: "A selectable checkbox component",
|
||||
icon: "ri-checkbox-line",
|
||||
props: {},
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [{ label: "Label", key: "label", control: Input }],
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "@budibase/standard-components/radiobutton",
|
||||
name: "Radiobutton",
|
||||
description: "A selectable radiobutton component",
|
||||
icon: "ri-radio-button-line",
|
||||
props: {},
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [{ label: "Label", key: "label", control: Input }],
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "@budibase/standard-components/select",
|
||||
|
@ -84,7 +167,10 @@ export default {
|
|||
description:
|
||||
"A select component for choosing from different options",
|
||||
icon: "ri-file-list-line",
|
||||
props: {},
|
||||
properties: {
|
||||
design: { ...all },
|
||||
settings: [],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -93,24 +179,51 @@ export default {
|
|||
name: "Button",
|
||||
description: "A basic html button that is ready for styling",
|
||||
icon: "ri-radio-button-fill",
|
||||
commonProps: {},
|
||||
children: [],
|
||||
properties: {
|
||||
design: {
|
||||
...all,
|
||||
},
|
||||
settings: [
|
||||
{ label: "Text", key: "text", control: Input },
|
||||
{
|
||||
label: "Disabled",
|
||||
key: "disabled",
|
||||
valueKey: "checked",
|
||||
control: Checkbox,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "@budibase/standard-components/icon",
|
||||
name: "Icon",
|
||||
description: "A basic component for displaying icons",
|
||||
icon: "ri-sun-fill",
|
||||
commonProps: {},
|
||||
children: [],
|
||||
properties: {
|
||||
design: { ...all },
|
||||
},
|
||||
},
|
||||
{
|
||||
_component: "@budibase/standard-components/link",
|
||||
name: "Link",
|
||||
description: "A basic link component for internal and external links",
|
||||
icon: "ri-link",
|
||||
commonProps: {},
|
||||
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:
|
||||
"A basic card component that can contain content and actions.",
|
||||
icon: "ri-layout-bottom-line",
|
||||
commonProps: {},
|
||||
children: [],
|
||||
properties: { design: { ...all } },
|
||||
},
|
||||
{
|
||||
_component: "@budibase/standard-components/login",
|
||||
name: "Login",
|
||||
description:
|
||||
"A component that automatically generates a login screen for your app.",
|
||||
icon: "ri-login-box-fill",
|
||||
commonProps: {},
|
||||
children: [],
|
||||
properties: { design: { ...all } },
|
||||
},
|
||||
{
|
||||
name: "Navigation Bar",
|
||||
|
@ -142,8 +254,8 @@ export default {
|
|||
description:
|
||||
"A component for handling the navigation within your app.",
|
||||
icon: "ri-navigation-fill",
|
||||
commonProps: {},
|
||||
children: [],
|
||||
properties: { design: { ...all } },
|
||||
},
|
||||
],
|
||||
},
|
||||
|
@ -153,19 +265,17 @@ export default {
|
|||
children: [
|
||||
{
|
||||
name: "Table",
|
||||
_component: "@budibase/materialdesign-components/Datatable",
|
||||
description: "A component that generates a table from your data.",
|
||||
icon: "ri-archive-drawer-fill",
|
||||
commonProps: {},
|
||||
properties: { design: { ...all } },
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
_component: "@budibase/materialdesign-components/Form",
|
||||
name: "Form",
|
||||
description: "A component that generates a form from your data.",
|
||||
icon: "ri-file-edit-fill",
|
||||
commonProps: {},
|
||||
component: "@budibase/materialdesign-components/Form",
|
||||
properties: { design: { ...all } },
|
||||
_component: "@budibase/materialdesign-components/Form",
|
||||
template: {
|
||||
component: "@budibase/materialdesign-components/Form",
|
||||
description: "Form for saving a record",
|
||||
|
@ -178,7 +288,7 @@ export default {
|
|||
name: "DataTable",
|
||||
description: "A table for displaying data from the backend.",
|
||||
icon: "ri-archive-drawer-fill",
|
||||
commonProps: {},
|
||||
properties: { design: { ...all } },
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
|
@ -186,7 +296,7 @@ export default {
|
|||
name: "DataForm",
|
||||
description: "Form stuff",
|
||||
icon: "ri-file-edit-fill",
|
||||
commonProps: {},
|
||||
properties: { design: { ...all } },
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
|
@ -194,7 +304,7 @@ export default {
|
|||
_component: "@budibase/standard-components/datachart",
|
||||
description: "Shiny chart",
|
||||
icon: "ri-bar-chart-line",
|
||||
commonProps: {},
|
||||
properties: { design: { ...all } },
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
|
@ -202,7 +312,7 @@ export default {
|
|||
_component: "@budibase/standard-components/datalist",
|
||||
description: "Shiny list",
|
||||
icon: "ri-file-list-line",
|
||||
commonProps: {},
|
||||
properties: { design: { ...all } },
|
||||
children: [],
|
||||
},
|
||||
{
|
||||
|
@ -210,7 +320,7 @@ export default {
|
|||
_component: "@budibase/standard-components/datamap",
|
||||
description: "Shiny map",
|
||||
icon: "ri-map-pin-line",
|
||||
commonProps: {},
|
||||
properties: { design: { ...all } },
|
||||
children: [],
|
||||
},
|
||||
],
|
||||
|
|
|
@ -38,12 +38,12 @@
|
|||
height: 100%;
|
||||
}
|
||||
|
||||
@media only screen and (min-width: 1800px) {
|
||||
.nav {
|
||||
overflow: auto;
|
||||
flex: 0 1 auto;
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
@media only screen and (min-width: 1800px) {
|
||||
.nav {
|
||||
overflow: auto;
|
||||
flex: 0 1 auto;
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,320 +1,55 @@
|
|||
import {
|
||||
generate_css,
|
||||
make_margin,
|
||||
generate_screen_css,
|
||||
generate_array_styles
|
||||
} 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", () => {
|
||||
test("it should generate a valid css rule: grid-area", () => {
|
||||
expect(generate_css({ layout: { gridarea: ["", "", "", ""] } })).toEqual({
|
||||
layout: "",
|
||||
position: "",
|
||||
})
|
||||
|
||||
test("Check how partially empty arrays are handled", () => {
|
||||
expect(["", "5", "", ""].map(generate_array_styles)).toEqual(["0px", "5px", "0px", "0px"])
|
||||
})
|
||||
|
||||
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("Check how array styles are output", () => {
|
||||
expect(generate_css({ margin: ["0", "10", "0", "15"] })).toBe("margin: 0px 10px 0px 15px;")
|
||||
})
|
||||
|
||||
test("it should generate a valid css rule: column 1", () => {
|
||||
expect(generate_css({ position: { column: ["", ""] } })).toEqual({
|
||||
layout: "",
|
||||
position: "",
|
||||
})
|
||||
test("Check handling of an array with empty string values", () => {
|
||||
expect(generate_css({ padding: ["", "", "", ""] })).toBe("")
|
||||
})
|
||||
|
||||
test("it should generate a valid css rule: column 2", () => {
|
||||
expect(generate_css({ position: { column: ["1", ""] } })).toEqual({
|
||||
position: "grid-column-start: 1;",
|
||||
layout: "",
|
||||
})
|
||||
test("Check handling of an empty array", () => {
|
||||
expect(generate_css({ margin: [] })).toBe("")
|
||||
})
|
||||
|
||||
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", () => {
|
||||
expect(
|
||||
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", () => {
|
||||
expect(generate_css({ position: { padding: ["1", "", "", "1"] } })).toEqual(
|
||||
{
|
||||
position: "padding: 1px 0px 0px 1px;",
|
||||
layout: "",
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test("it should generate a valid css rule: margin 1", () => {
|
||||
expect(
|
||||
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", () => {
|
||||
expect(generate_css({ position: { margin: ["1", "", "", "1"] } })).toEqual({
|
||||
position: "margin: 1px 0px 0px 1px;",
|
||||
layout: "",
|
||||
})
|
||||
})
|
||||
|
||||
test("it should generate a valid css rule: z-index 1", () => {
|
||||
expect(generate_css({ position: { zindex: "" } })).toEqual({
|
||||
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: "",
|
||||
})
|
||||
test("Check handling of valid font property", () => {
|
||||
expect(generate_css({ "font-size": "10px" })).toBe("font-size: 10px;")
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
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 normalComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: { "font-size": "16px" }, hover: {}, active: {}, selected: {} } }
|
||||
|
||||
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)
|
||||
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 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 hoverComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {"font-size": "16px"}, active: {}, selected: {} } }
|
||||
|
||||
const compiled = `.pos-1 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-1 {
|
||||
test("Test generation of hover css styles", () => {
|
||||
expect(generate_screen_css([hoverComponent])).toBe(".header-123-456:hover {\nfont-size: 16px;\n}")
|
||||
})
|
||||
|
||||
}
|
||||
.pos-2 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-2 {
|
||||
const selectedComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: { "font-size": "16px" } } }
|
||||
|
||||
}
|
||||
.pos-3 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-3 {
|
||||
test("Test generation of selection css styles", () => {
|
||||
expect(generate_screen_css([selectedComponent])).toBe(".header-123-456::selection {\nfont-size: 16px;\n}")
|
||||
})
|
||||
|
||||
}
|
||||
.pos-4 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-4 {
|
||||
const emptyComponent = { _id: "123-456", _component: "@standard-components/header", _children: [], _styles: { normal: {}, hover: {}, active: {}, selected: {} } }
|
||||
|
||||
}
|
||||
.pos-5 {
|
||||
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 {
|
||||
|
||||
}
|
||||
.pos-8 {
|
||||
margin: 1px 1px 1px 1px;
|
||||
}
|
||||
.lay-8 {
|
||||
|
||||
}`
|
||||
|
||||
expect(generate_screen_css(components)).toEqual(compiled)
|
||||
test.only("Testing handling of empty component styles", () => {
|
||||
expect(generate_screen_css([emptyComponent])).toBe("")
|
||||
})
|
||||
})
|
|
@ -9,8 +9,10 @@
|
|||
"_id": 0,
|
||||
"type": "div",
|
||||
"_styles": {
|
||||
"layout": {},
|
||||
"position": {}
|
||||
"normal": {},
|
||||
"hover": {},
|
||||
"active": {},
|
||||
"selected": {}
|
||||
},
|
||||
"_code": ""
|
||||
},
|
||||
|
|
|
@ -9,8 +9,10 @@
|
|||
"_id": 1,
|
||||
"type": "div",
|
||||
"_styles": {
|
||||
"layout": {},
|
||||
"position": {}
|
||||
"normal": {},
|
||||
"hover": {},
|
||||
"active": {},
|
||||
"selected": {}
|
||||
},
|
||||
"_code": ""
|
||||
},
|
||||
|
|
|
@ -9,8 +9,10 @@
|
|||
"_id": 0,
|
||||
"type": "div",
|
||||
"_styles": {
|
||||
"layout": {},
|
||||
"position": {}
|
||||
"normal": {},
|
||||
"hover": {},
|
||||
"active": {},
|
||||
"selected": {}
|
||||
},
|
||||
"_code": ""
|
||||
},
|
||||
|
|
|
@ -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 = []
|
||||
for (let childProps of treeNode.props._children) {
|
||||
|
|
|
@ -35,8 +35,9 @@ export const prepareRenderComponent = ({
|
|||
thisNode.rootElement =
|
||||
htmlElement.children[htmlElement.children.length - 1]
|
||||
|
||||
let [componentName] = props._component.match(/[a-z]*$/)
|
||||
if (props._id && thisNode.rootElement) {
|
||||
thisNode.rootElement.classList.add(`pos-${props._id}`)
|
||||
thisNode.rootElement.classList.add(`${componentName}-${props._id}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,10 +12,10 @@
|
|||
"props": {
|
||||
"logoUrl": "string",
|
||||
"title": "string",
|
||||
"backgroundColor": "colour",
|
||||
"color": "colour",
|
||||
"backgroundColor": "string",
|
||||
"color": "string",
|
||||
"borderWidth": "string",
|
||||
"borderColor": "colour",
|
||||
"borderColor": "string",
|
||||
"borderStyle": "string"
|
||||
}
|
||||
},
|
||||
|
@ -23,35 +23,10 @@
|
|||
"name": "Button",
|
||||
"description": "an html <button />",
|
||||
"props": {
|
||||
"contentText": {
|
||||
"type": "string",
|
||||
"default": "Button"
|
||||
},
|
||||
"text": "string",
|
||||
"className": "string",
|
||||
"disabled": "bool",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
"onClick": "event"
|
||||
},
|
||||
"tags": [
|
||||
"layout"
|
||||
|
@ -167,58 +142,7 @@
|
|||
"children": false,
|
||||
"props": {
|
||||
"text": "string",
|
||||
"color": "colour",
|
||||
"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"
|
||||
]
|
||||
}
|
||||
"type": {"type": "string", "default": "none"}
|
||||
},
|
||||
"tags": [
|
||||
"div",
|
||||
|
@ -230,6 +154,7 @@
|
|||
"description": "A component that allows the user to input text.",
|
||||
"props": {
|
||||
"label": "string",
|
||||
"type": "string",
|
||||
"value": "string",
|
||||
"onchange": "event"
|
||||
}
|
||||
|
@ -259,7 +184,7 @@
|
|||
"props": {
|
||||
"icon": "string",
|
||||
"fontSize": "string",
|
||||
"color": "colour"
|
||||
"color": "string"
|
||||
}
|
||||
},
|
||||
"datatable": {
|
||||
|
@ -328,8 +253,8 @@
|
|||
"url": "string",
|
||||
"openInNewTab": "bool",
|
||||
"text": "string",
|
||||
"color": "colour",
|
||||
"hoverColor": "colour",
|
||||
"color": "string",
|
||||
"hoverColor": "string",
|
||||
"underline": "bool",
|
||||
"fontSize": "string",
|
||||
"fontFamily": {
|
||||
|
@ -383,25 +308,6 @@
|
|||
"summary"
|
||||
],
|
||||
"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,
|
||||
|
@ -416,7 +322,6 @@
|
|||
"description": "An HTML H1 - H6 tag",
|
||||
"props": {
|
||||
"className": "string",
|
||||
"color":"colour",
|
||||
"text": "string",
|
||||
"type": {
|
||||
"type": "options",
|
||||
|
@ -429,21 +334,6 @@
|
|||
"h5",
|
||||
"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": []
|
||||
|
|
|
@ -1,58 +1,15 @@
|
|||
<script>
|
||||
import { cssVars, createClasses } from "./cssVars"
|
||||
import { buildStyle } from "./buildStyle"
|
||||
export let className = "default"
|
||||
export let disabled = false
|
||||
export let contentText
|
||||
export let text
|
||||
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
|
||||
let theButton
|
||||
let cssVariables
|
||||
let buttonStyles
|
||||
|
||||
let customHoverColorClass
|
||||
let customHoverBorderClass
|
||||
let customHoverBackClass
|
||||
|
||||
let customClasses = ""
|
||||
|
||||
$: if (_bb.props._children && _bb.props._children.length > 0)
|
||||
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 = () => {
|
||||
_bb.call(onClick)
|
||||
}
|
||||
|
@ -60,15 +17,10 @@
|
|||
|
||||
<button
|
||||
bind:this={theButton}
|
||||
use:cssVars={cssVariables}
|
||||
class="{className}
|
||||
{customClasses}"
|
||||
class={className}
|
||||
disabled={disabled || false}
|
||||
on:click={clickHandler}
|
||||
style={buttonStyles}>
|
||||
{#if !_bb.props._children || _bb.props._children.length === 0}
|
||||
{contentText}
|
||||
{/if}
|
||||
on:click={clickHandler}>
|
||||
{#if !_bb.props._children || _bb.props._children.length === 0}{text}{/if}
|
||||
</button>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -4,26 +4,12 @@
|
|||
export let className = ""
|
||||
export let onLoad
|
||||
export let type = "div"
|
||||
export let backgroundColor
|
||||
export let color
|
||||
export let borderWidth
|
||||
export let borderColor
|
||||
export let borderStyle
|
||||
export let _bb
|
||||
|
||||
let containerElement
|
||||
let hasLoaded
|
||||
let currentChildren
|
||||
|
||||
$: cssVariables = {
|
||||
backgroundColor,
|
||||
color,
|
||||
borderWidth,
|
||||
borderColor,
|
||||
borderStyle,
|
||||
}
|
||||
$: classes = `${createClasses(cssVariables)} ${className}`
|
||||
|
||||
$: {
|
||||
if (containerElement) {
|
||||
_bb.attachChildren(containerElement)
|
||||
|
@ -36,87 +22,29 @@
|
|||
</script>
|
||||
|
||||
{#if type === 'div'}
|
||||
<div
|
||||
class={classes}
|
||||
bind:this={containerElement}
|
||||
use:cssVars={cssVariables} />
|
||||
<div bind:this={containerElement} />
|
||||
{:else if type === 'header'}
|
||||
<header
|
||||
class={classes}
|
||||
bind:this={containerElement}
|
||||
use:cssVars={cssVariables} />
|
||||
<header bind:this={containerElement} />
|
||||
{:else if type === 'main'}
|
||||
<main
|
||||
class={classes}
|
||||
bind:this={containerElement}
|
||||
use:cssVars={cssVariables} />
|
||||
<main bind:this={containerElement} />
|
||||
{:else if type === 'footer'}
|
||||
<footer
|
||||
class={classes}
|
||||
bind:this={containerElement}
|
||||
use:cssVars={cssVariables} />
|
||||
<footer bind:this={containerElement} />
|
||||
{:else if type === 'aside'}
|
||||
<aside
|
||||
class={classes}
|
||||
bind:this={containerElement}
|
||||
use:cssVars={cssVariables} />
|
||||
<aside bind:this={containerElement} />
|
||||
{:else if type === 'summary'}
|
||||
<summary
|
||||
class={classes}
|
||||
bind:this={containerElement}
|
||||
use:cssVars={cssVariables} />
|
||||
<summary bind:this={containerElement} />
|
||||
{:else if type === 'details'}
|
||||
<details
|
||||
class={classes}
|
||||
bind:this={containerElement}
|
||||
use:cssVars={cssVariables} />
|
||||
<details bind:this={containerElement} />
|
||||
{:else if type === 'article'}
|
||||
<article
|
||||
class={classes}
|
||||
bind:this={containerElement}
|
||||
use:cssVars={cssVariables} />
|
||||
<article bind:this={containerElement} />
|
||||
{:else if type === 'nav'}
|
||||
<nav
|
||||
class={classes}
|
||||
bind:this={containerElement}
|
||||
use:cssVars={cssVariables} />
|
||||
<nav bind:this={containerElement} />
|
||||
{:else if type === 'mark'}
|
||||
<mark
|
||||
class={classes}
|
||||
bind:this={containerElement}
|
||||
use:cssVars={cssVariables} />
|
||||
<mark bind:this={containerElement} />
|
||||
{:else if type === 'figure'}
|
||||
<figure
|
||||
class={classes}
|
||||
bind:this={containerElement}
|
||||
use:cssVars={cssVariables} />
|
||||
<figure bind:this={containerElement} />
|
||||
{:else if type === 'figcaption'}
|
||||
<figcaption
|
||||
class={classes}
|
||||
bind:this={containerElement}
|
||||
use:cssVars={cssVariables} />
|
||||
<figcaption bind:this={containerElement} />
|
||||
{:else if type === 'paragraph'}
|
||||
<p class={classes} bind:this={containerElement} use:cssVars={cssVariables} />
|
||||
<p bind:this={containerElement} />
|
||||
{/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>
|
||||
|
|
|
@ -2,28 +2,25 @@
|
|||
import { buildStyle } from "./buildStyle.js"
|
||||
export let className = ""
|
||||
export let type
|
||||
export let _bb
|
||||
export let text = ""
|
||||
export let fontFamily = ""
|
||||
export let color = ""
|
||||
|
||||
export let _bb
|
||||
|
||||
let containerElement
|
||||
|
||||
$: containerElement && !text && _bb.attachChildren(containerElement)
|
||||
$: style = buildStyle({ "font-family": fontFamily, color })
|
||||
// $: console.log("HEADING", color)
|
||||
</script>
|
||||
|
||||
{#if type === 'h1'}
|
||||
<h1 class={className} {style} bind:this={containerElement}>{text}</h1>
|
||||
<h1 class={className} bind:this={containerElement}>{text}</h1>
|
||||
{: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'}
|
||||
<h3 class={className} {style} bind:this={containerElement}>{text}</h3>
|
||||
<h3 class={className} bind:this={containerElement}>{text}</h3>
|
||||
{: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'}
|
||||
<h5 class={className} {style} bind:this={containerElement}>{text}</h5>
|
||||
<h5 class={className} bind:this={containerElement}>{text}</h5>
|
||||
{:else if type === 'h6'}
|
||||
<h6 class={className} {style} bind:this={containerElement}>{text}</h6>
|
||||
<h6 class={className} bind:this={containerElement}>{text}</h6>
|
||||
{/if}
|
||||
|
|
|
@ -4,11 +4,6 @@
|
|||
export let url = ""
|
||||
export let text = ""
|
||||
export let openInNewTab = false
|
||||
export let color
|
||||
export let hoverColor
|
||||
export let underline = false
|
||||
export let fontFamily
|
||||
export let fontSize
|
||||
|
||||
export let _bb
|
||||
|
||||
|
@ -16,43 +11,12 @@
|
|||
|
||||
$: anchorElement && !text && _bb.attachChildren(anchorElement)
|
||||
$: target = openInNewTab ? "_blank" : "_self"
|
||||
$: cssVariables = {
|
||||
hoverColor,
|
||||
color,
|
||||
textDecoration: underline ? "underline" : "none",
|
||||
fontSize,
|
||||
fontFamily,
|
||||
}
|
||||
$: classes = createClasses(cssVariables)
|
||||
</script>
|
||||
|
||||
<a
|
||||
href={url}
|
||||
bind:this={anchorElement}
|
||||
class={classes}
|
||||
{target}
|
||||
use:cssVars={cssVariables}>
|
||||
{text}
|
||||
</a>
|
||||
<a href={url} bind:this={anchorElement} {target}>{text}</a>
|
||||
|
||||
<style>
|
||||
.color {
|
||||
color: var(--color);
|
||||
}
|
||||
|
||||
.hoverColor:hover {
|
||||
color: var(--color);
|
||||
}
|
||||
|
||||
.textDecoration {
|
||||
text-decoration: var(--textDecoration);
|
||||
}
|
||||
|
||||
.fontSize {
|
||||
font-size: var(--fontSize);
|
||||
}
|
||||
|
||||
.fontFamily {
|
||||
font-family: var(--fontFamily);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -4,45 +4,35 @@
|
|||
export let text = ""
|
||||
export let className = ""
|
||||
|
||||
export let formattingTag = ""
|
||||
|
||||
export let fontFamily = ""
|
||||
export let fontSize = "1em"
|
||||
export let textAlign = ""
|
||||
export let verticalAlign = ""
|
||||
export let color = ""
|
||||
export let type = ""
|
||||
|
||||
export let _bb
|
||||
|
||||
const isTag = tag => (formattingTag || "").indexOf(tag) > -1
|
||||
|
||||
$: style = buildStyle({
|
||||
"font-size": fontSize,
|
||||
"font-family": fontFamily,
|
||||
color,
|
||||
})
|
||||
const isTag = tag => type === tag
|
||||
</script>
|
||||
|
||||
{#if isTag('none')}
|
||||
<span {style}>{text}</span>
|
||||
{:else if isTag('<b>')}
|
||||
<b class={className} {style}>{text}</b>
|
||||
{:else if isTag('<strong>')}
|
||||
<strong class={className} {style}>{text}</strong>
|
||||
{:else if isTag('<i>')}
|
||||
<i class={className} {style}>{text}</i>
|
||||
{:else if isTag('<em>')}
|
||||
<em class={className} {style}>{text}</em>
|
||||
{:else if isTag('<mark>')}
|
||||
<mark class={className} {style}>{text}</mark>
|
||||
{:else if isTag('<small>')}
|
||||
<small class={className} {style}>{text}</small>
|
||||
{:else if isTag('<del>')}
|
||||
<del class={className} {style}>{text}</del>
|
||||
{:else if isTag('<ins>')}
|
||||
<ins class={className} {style}>{text}</ins>
|
||||
{:else if isTag('<sub>')}
|
||||
<sub class={className} {style}>{text}</sub>
|
||||
{:else if isTag('<sup>')}
|
||||
<sup class={className} {style}>{text}</sup>
|
||||
{:else}{text}{/if}
|
||||
<span>{text}</span>
|
||||
{:else if isTag('bold')}
|
||||
<b class={className}>{text}</b>
|
||||
{:else if isTag('strong')}
|
||||
<strong class={className}>{text}</strong>
|
||||
{:else if isTag('italic')}
|
||||
<i class={className}>{text}</i>
|
||||
{:else if isTag('emphasis')}
|
||||
<em class={className}>{text}</em>
|
||||
{:else if isTag('mark')}
|
||||
<mark class={className}>{text}</mark>
|
||||
{:else if isTag('small')}
|
||||
<small class={className}>{text}</small>
|
||||
{:else if isTag('del')}
|
||||
<del class={className}>{text}</del>
|
||||
{:else if isTag('ins')}
|
||||
<ins class={className}>{text}</ins>
|
||||
{:else if isTag('sub')}
|
||||
<sub class={className}>{text}</sub>
|
||||
{:else if isTag('sup')}
|
||||
<sup class={className}>{text}</sup>
|
||||
{:else}
|
||||
<span>{text}</span>
|
||||
{/if}
|
||||
|
|
Loading…
Reference in New Issue