This commit is contained in:
Martin McKeaveney 2020-08-19 17:14:30 +01:00
commit d2638e0150
199 changed files with 1798 additions and 8698 deletions

View File

@ -28,9 +28,5 @@
"format": "prettier --write \"{,!(node_modules)/**/}*.{js,jsx,svelte}\"",
"test:e2e": "lerna run cy:test",
"test:e2e:ci": "lerna run cy:ci"
},
"dependencies": {
"@material/icon-button": "4.0.0",
"date-fns": "^2.10.0"
}
}

View File

@ -131,9 +131,9 @@ Cypress.Commands.add("navigateToFrontend", () => {
Cypress.Commands.add("createScreen", (screenName, route) => {
cy.get(".newscreen").click()
cy.get(".uk-input:first").type(screenName)
cy.get("[data-cy=new-screen-dialog] input:first").type(screenName)
if (route) {
cy.get(".uk-input:last").type(route)
cy.get("[data-cy=new-screen-dialog] input:last").type(route)
}
cy.get(".uk-modal-footer").within(() => {
cy.contains("Create Screen").click()

View File

@ -60,24 +60,18 @@
"@budibase/bbui": "^1.24.1",
"@budibase/client": "^0.1.17",
"@budibase/colorpicker": "^1.0.1",
"@nx-js/compiler-util": "^2.0.0",
"@sentry/browser": "5.19.1",
"@svelteschool/svelte-forms": "^0.7.0",
"britecharts": "^2.16.0",
"codemirror": "^5.51.0",
"d3-selection": "^1.4.1",
"date-fns": "^1.29.0",
"deepmerge": "^4.2.2",
"fast-sort": "^2.2.0",
"feather-icons": "^4.21.0",
"flatpickr": "^4.5.7",
"lodash": "^4.17.13",
"lunr": "^2.3.5",
"mustache": "^4.0.1",
"posthog-js": "1.3.1",
"safe-buffer": "^5.1.2",
"shortid": "^2.2.15",
"string_decoder": "^1.2.0",
"svelte-portal": "^0.1.0",
"svelte-simple-modal": "^0.4.2",
"uikit": "^3.1.7",
@ -94,19 +88,15 @@
"@testing-library/jest-dom": "^5.11.0",
"@testing-library/svelte": "^3.0.0",
"babel-jest": "^24.8.0",
"browser-sync": "^2.26.7",
"cypress": "^4.8.0",
"cypress-terminal-report": "^1.4.1",
"eslint-plugin-cypress": "^2.11.1",
"http-proxy-middleware": "^0.19.1",
"identity-obj-proxy": "^3.0.0",
"jest": "^24.8.0",
"ncp": "^2.0.0",
"npm-run-all": "^4.1.5",
"rimraf": "^3.0.2",
"rollup": "^1.12.0",
"rollup-plugin-alias": "^1.5.2",
"rollup-plugin-browsersync": "^1.0.0",
"rollup-plugin-commonjs": "^10.0.0",
"rollup-plugin-copy": "^3.0.0",
"rollup-plugin-livereload": "^1.0.0",

View File

@ -1,4 +1,4 @@
import { values } from "lodash/fp"
import { values, cloneDeep } from "lodash/fp"
import { get_capitalised_name } from "../../helpers"
import { backendUiStore } from "builderStore"
import * as backendStoreActions from "./backend"
@ -24,8 +24,8 @@ import {
saveCurrentPreviewItem as _saveCurrentPreviewItem,
saveScreenApi as _saveScreenApi,
regenerateCssForCurrentScreen,
generateNewIdsForComponent,
} from "../storeUtils"
export const getStore = () => {
const initial = {
apps: [],
@ -70,6 +70,8 @@ export const getStore = () => {
store.addTemplatedComponent = addTemplatedComponent(store)
store.setMetadataProp = setMetadataProp(store)
store.editPageOrScreen = editPageOrScreen(store)
store.pasteComponent = pasteComponent(store)
store.storeComponentForCopy = storeComponentForCopy(store)
return store
}
@ -291,9 +293,14 @@ const addChildComponent = store => (componentToAdd, presetProps = {}) => {
state
)
state.currentComponentInfo._children = state.currentComponentInfo._children.concat(
newComponent.props
)
const currentComponent =
state.components[state.currentComponentInfo._component]
const targetParent = currentComponent.children
? state.currentComponentInfo
: getParent(state.currentPreviewItem.props, state.currentComponentInfo)
targetParent._children = targetParent._children.concat(newComponent.props)
state.currentFrontEndType === "page"
? _savePage(state)
@ -454,3 +461,50 @@ const setMetadataProp = store => (name, prop) => {
return s
})
}
const storeComponentForCopy = store => (component, cut = false) => {
store.update(s => {
const copiedComponent = cloneDeep(component)
s.componentToPaste = copiedComponent
s.componentToPaste.isCut = cut
if (cut) {
const parent = getParent(s.currentPreviewItem.props, component._id)
parent._children = parent._children.filter(c => c._id !== component._id)
selectComponent(s, parent)
}
return s
})
}
const pasteComponent = store => (targetComponent, mode) => {
store.update(s => {
if (!s.componentToPaste) return s
const componentToPaste = cloneDeep(s.componentToPaste)
// retain the same ids as things may be referencing this component
if (componentToPaste.isCut) {
// in case we paste a second time
s.componentToPaste.isCut = false
} else {
generateNewIdsForComponent(componentToPaste)
}
delete componentToPaste.isCut
if (mode === "inside") {
targetComponent._children.push(componentToPaste)
return s
}
const parent = getParent(s.currentPreviewItem.props, targetComponent)
const targetIndex = parent._children.indexOf(targetComponent)
const index = mode === "above" ? targetIndex : targetIndex + 1
parent._children.splice(index, 0, cloneDeep(componentToPaste))
regenerateCssForCurrentScreen(s)
_saveCurrentPreviewItem(s)
selectComponent(s, componentToPaste)
return s
})
}

View File

@ -1,6 +1,7 @@
import { makePropsSafe } from "components/userInterface/pagesParsing/createProps"
import api from "./api"
import { generate_screen_css } from "./generate_css"
import { uuid } from "./uuid"
export const selectComponent = (state, component) => {
const componentDef = component._component.startsWith("##")
@ -79,3 +80,8 @@ export const regenerateCssForCurrentScreen = state => {
])
return state
}
export const generateNewIdsForComponent = c =>
walkProps(c, p => {
p._id = uuid()
})

View File

@ -1,59 +0,0 @@
<script>
export let title
export let icon
export let primary
export let secondary
export let tertiary
</script>
<div on:click class:primary class:secondary class:tertiary>
<i class={icon} />
<span>{title}</span>
</div>
<style>
div {
height: 80px;
border-radius: 5px;
color: var(--ink);
font-weight: 400;
padding: 15px;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
transition: 0.3s transform;
background: var(--grey-1);
}
i {
font-size: 24px;
color: var(--grey-7);
}
span {
font-size: 14px;
text-align: center;
margin-top: 8px;
line-height: 1.25;
}
div:hover {
cursor: pointer;
background: var(--grey-2);
}
.primary {
background: var(--ink);
color: var(--white);
}
.secondary {
background: var(--blue-light);
}
.tertiary {
background: var(--white);
}
</style>

View File

@ -1,20 +0,0 @@
<script>
import { JavaScriptIcon } from "../common/Icons"
// todo: use https://ace.c9.io
export let text = ""
</script>
<textarea class="uk-textarea" bind:value={text} />
<style>
textarea {
padding: 10px;
margin-top: 5px;
margin-bottom: 10px;
background: var(--grey-7);
color: var(--white);
font-family: "Courier New", Courier, monospace;
height: 200px;
border-radius: 5px;
}
</style>

View File

@ -1,13 +0,0 @@
<script>
export let name = ""
</script>
<div>
<h4>Coming Sometime: {name}</h4>
</div>
<style>
h4 {
margin-top: 20px;
}
</style>

View File

@ -29,8 +29,10 @@
}
const ok = () => {
const result = onOk()
// allow caller to return false, to cancel the "ok"
if (result === false) return
hide()
onOk()
}
</script>

View File

@ -1,45 +0,0 @@
<script>
import { createEventDispatcher } from "svelte"
import Select from "../common/Select.svelte"
export let selected
export let label
export let options
export let valueMember
export let textMember
export let multiple = false
export let width = "medium"
export let size = "small"
const dispatch = createEventDispatcher()
</script>
<div class="uk-margin">
<label class="uk-form-label">{label}</label>
<div class="uk-form-controls">
{#if multiple}
<Select
class="uk-select uk-form-width-{width} uk-form-{size}"
multiple
bind:value={selected}
on:change>
{#each options as option}
<option value={!valueMember ? option : valueMember(option)}>
{!textMember ? option : textMember(option)}
</option>
{/each}
</Select>
{:else}
<Select
class="uk-select uk-form-width-{width} uk-form-{size}"
bind:value={selected}
on:change>
{#each options as option}
<option value={!valueMember ? option : valueMember(option)}>
{!textMember ? option : textMember(option)}
</option>
{/each}
</Select>
{/if}
</div>
</div>

View File

@ -1,62 +0,0 @@
<script>
import { onMount } from "svelte"
import { buildStyle } from "../../helpers.js"
export let value = ""
export let name = ""
export let textAlign = "left"
export let width = "160px"
export let placeholder = ""
export let suffix = ""
export let onChange = val => {}
let centerPlaceholder = textAlign === "center"
let style = buildStyle({ width, textAlign })
function handleChange(val) {
value = val
let _value = value !== "auto" ? value + suffix : value
onChange(_value)
}
$: displayValue =
suffix && value && value.endsWith(suffix)
? value.replace(new RegExp(`${suffix}$`), "")
: value || ""
</script>
<input
{name}
class:centerPlaceholder
type="text"
value={displayValue}
{placeholder}
{style}
on:change={e => handleChange(e.target.value)} />
<style>
input {
/* width: 32px; */
height: 36px;
font-size: 14px;
font-weight: 400;
margin: 0px 0px 0px 2px;
color: var(--ink);
padding: 0px 8px;
font-family: inter;
width: 164px;
box-sizing: border-box;
background-color: var(--grey-2);
border-radius: 4px;
border: 1px solid var(--grey-2);
outline: none;
}
input::placeholder {
text-align: left;
}
.centerPlaceholder::placeholder {
text-align: center;
}
</style>

View File

@ -1,50 +0,0 @@
<script>
import { onMount } from "svelte"
import Input from "../Input.svelte"
export let meta = []
export let label = ""
export let value = ["0", "0", "0", "0"]
export let suffix = ""
export let onChange = () => {}
function handleChange(val, idx) {
value.splice(idx, 1, val !== "auto" && suffix ? val + suffix : val)
value = value
let _value = value.map(v =>
suffix && !v.endsWith(suffix) && v !== "auto" ? v + suffix : v
)
onChange(_value)
}
$: displayValues =
value && suffix
? value.map(v => v.replace(new RegExp(`${suffix}$`), ""))
: value || []
</script>
<div class="input-container">
<div class="label">{label}</div>
<div class="inputs-group">
{#each meta as m, i}
<Input
width="37px"
textAlign="center"
placeholder={m.placeholder || ''}
value={!displayValues || displayValues[i] === '0' ? '' : displayValues[i]}
onChange={value => handleChange(value || 0, i)} />
{/each}
</div>
</div>
<style>
.label {
flex: 0;
}
.inputs-group {
flex: 1;
}
</style>

View File

@ -1,23 +0,0 @@
<button on:click>
<slot>+</slot>
</button>
<style>
button {
cursor: pointer;
outline: none;
border: none;
border-radius: 5px;
min-width: 1.8rem;
min-height: 1.8rem;
padding-bottom: 10px;
display: flex;
justify-content: center;
align-items: center;
font-size: 1.2rem;
font-weight: 600;
color: var(--ink);
}
</style>

View File

@ -1,65 +0,0 @@
<script>
import getIcon from "./icon"
export let icon
export let value
</script>
<div class="select-container">
{#if icon}
<i class={icon} />
{/if}
<select class:adjusted={icon} on:change bind:value>
<slot />
</select>
<span class="arrow">
{@html getIcon('chevron-down', '24')}
</span>
</div>
<style>
.select-container {
font-size: 14px;
position: relative;
border: var(--grey-4) 1px solid;
}
.adjusted {
padding-left: 30px;
}
i {
position: absolute;
left: 10px;
top: 10px;
}
select {
height: 40px;
display: block;
font-family: sans-serif;
font-weight: 400;
font-size: 14px;
color: var(--ink);
padding: 0 40px 0px 20px;
width: 100%;
max-width: 100%;
box-sizing: border-box;
margin: 0;
-moz-appearance: none;
-webkit-appearance: none;
appearance: none;
background: var(--white);
}
.arrow {
position: absolute;
right: 10px;
bottom: 0;
margin: auto;
width: 30px;
height: 30px;
pointer-events: none;
color: var(--ink);
}
</style>

View File

@ -1,33 +0,0 @@
<script>
export let text = ""
export let label = ""
export let width = "medium"
export let size = "small"
export let margin = true
export let infoText = ""
export let hasError = false
export let disabled = false
</script>
<div class:uk-margin={margin}>
<label class="uk-form-label">{label}</label>
<div class="uk-form-controls">
<input
data-cy={label}
class="budibase__input"
class:uk-form-danger={hasError}
on:change
bind:value={text}
{disabled} />
</div>
{#if infoText}
<div class="info-text">{infoText}</div>
{/if}
</div>
<style>
.info-text {
font-size: 0.7rem;
color: var(--secondary50);
}
</style>

View File

@ -1,16 +0,0 @@
<script>
import RecordCard from "./RecordCard.svelte"
export let record = {}
</script>
<div class="root">
<div class="title">{record.name}</div>
<div class="inner">
<div class="node-path">{record.nodeKey()}</div>
</div>
</div>
<style>
</style>

View File

@ -4,7 +4,6 @@
import getOr from "lodash/fp/getOr"
import { store, backendUiStore } from "builderStore"
import { Button, Icon } from "@budibase/bbui"
import Select from "components/common/Select.svelte"
import ActionButton from "components/common/ActionButton.svelte"
import LinkedRecord from "./LinkedRecord.svelte"
import TablePagination from "./TablePagination.svelte"
@ -157,6 +156,9 @@
max-width: 200px;
text-overflow: ellipsis;
border: 1px solid var(--grey-4);
overflow: hidden;
white-space: pre;
box-sizing: border-box;
}
tbody tr {
@ -176,7 +178,7 @@
.popovers {
display: flex;
gap: var(--spacing-l);
gap: var(--spacing-m);
}
.no-data {

View File

@ -1,11 +1,10 @@
<script>
import { onMount } from "svelte"
import { Input, TextArea, Button, Select } from "@budibase/bbui"
import { cloneDeep, merge } from "lodash/fp"
import { store, backendUiStore } from "builderStore"
import { FIELDS } from "constants/backend"
import { notifier } from "builderStore/store/notifications"
import Dropdown from "components/common/Dropdown.svelte"
import Textbox from "components/common/Textbox.svelte"
import ButtonGroup from "components/common/ButtonGroup.svelte"
import NumberBox from "components/common/NumberBox.svelte"
import ValuesList from "components/common/ValuesList.svelte"
@ -19,6 +18,7 @@
export let onClosed
export let field = {}
let fieldDefinitions = cloneDeep(FIELDS)
let originalName = field.name
$: required =
@ -26,7 +26,10 @@
field.constraints.presence &&
!field.constraints.presence.allowEmpty
$: if (field.type) {
field.constraints = FIELDS[field.type.toUpperCase()].constraints
field.constraints = merge(
fieldDefinitions[field.type.toUpperCase()].constraints,
field.constraints
)
}
async function saveColumn() {
@ -46,7 +49,7 @@
<Input placeholder="Name" thin bind:value={field.name} />
<Select secondary thin bind:value={field.type}>
{#each Object.values(FIELDS) as field}
{#each Object.values(fieldDefinitions) as field}
<option value={field.type}>{field.name}</option>
{/each}
</Select>

View File

@ -5,7 +5,6 @@
import { compose, map, get, flatten } from "lodash/fp"
import { Input, TextArea, Button } from "@budibase/bbui"
import LinkedRecordSelector from "components/common/LinkedRecordSelector.svelte"
import Select from "components/common/Select.svelte"
import RecordFieldControl from "./RecordFieldControl.svelte"
import * as api from "../api"
import ErrorsBox from "components/common/ErrorsBox.svelte"

View File

@ -1,3 +1,2 @@
export { default as DeleteRecordModal } from "./DeleteRecord.svelte"
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte"
export { default as CreateUserModal } from "./CreateUser.svelte"

View File

@ -25,6 +25,7 @@
function deleteField() {
backendUiStore.actions.models.deleteField(field)
hideEditor()
}
function sort(direction, column) {

View File

@ -1,95 +0,0 @@
<script>
import { tick, onMount } from "svelte"
import { goto } from "@sveltech/routify"
import { store, backendUiStore } from "builderStore"
import api from "builderStore/api"
import { CheckIcon } from "../common/Icons"
$: instances = $store.appInstances
async function selectDatabase(database) {
backendUiStore.actions.database.select(database)
}
async function deleteDatabase(database) {
const DELETE_DATABASE_URL = `/api/instances/${database.name}`
const response = await api.delete(DELETE_DATABASE_URL)
store.update(state => {
state.appInstances = state.appInstances.filter(
db => db._id !== database._id
)
return state
})
}
</script>
<div class="root">
<ul>
{#each $store.appInstances as database}
<li>
<span class="icon">
{#if database._id === $backendUiStore.selectedDatabase._id}
<CheckIcon />
{/if}
</span>
<button
class:active={database._id === $backendUiStore.selectedDatabase._id}
on:click={() => {
$goto(`./database/${database._id}`), selectDatabase(database)
}}>
{database.name}
</button>
<i
class="ri-delete-bin-7-line hoverable alignment"
on:click={() => deleteDatabase(database)} />
</li>
{/each}
</ul>
</div>
<style>
.root {
font-size: 13px;
color: var(--ink);
position: relative;
padding-left: 20px;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
.alignment {
margin-left: auto;
padding-right: 20px;
}
li {
margin: 0px 0px 10px 0px;
display: flex;
align-items: center;
}
button {
margin: 0 0 0 6px;
padding: 0;
border: none;
font-size: 13px;
outline: none;
cursor: pointer;
background: rgba(0, 0, 0, 0);
text-rendering: optimizeLegibility;
}
.active {
font-weight: 500;
}
.icon {
display: inline-block;
width: 14px;
color: #333;
}
</style>

View File

@ -17,6 +17,12 @@
})
notifier.success(`Table ${name} created successfully.`)
$goto(`./model/${model._id}`)
name = ""
dropdown.hide()
}
const onClosed = () => {
name = ""
dropdown.hide()
}
</script>
@ -35,7 +41,7 @@
</div>
<footer>
<div class="button-margin-3">
<Button secondary on:click={dropdown.hide}>Cancel</Button>
<Button secondary on:click={onClosed}>Cancel</Button>
</div>
<div class="button-margin-4">
<Button primary on:click={saveTable}>Save</Button>

View File

@ -1,82 +0,0 @@
<script>
import { onMount } from "svelte"
import { store, backendUiStore } from "builderStore"
import api from "builderStore/api"
import getIcon from "../common/icon"
import { CheckIcon } from "../common/Icons"
const getPage = (s, name) => {
const props = s.pages[name]
return { name, props }
}
$: currentAppInfo = {
name: $store.name,
}
async function fetchUsers() {
const FETCH_USERS_URL = `/api/users`
const response = await api.get(FETCH_USERS_URL)
const users = await response.json()
backendUiStore.update(state => {
state.users = users
return state
})
}
onMount(fetchUsers)
</script>
<div class="root">
<ul>
{#each $backendUiStore.users as user}
<li>
<i class="ri-user-4-line" />
<button class:active={user.username === $store.currentUserName}>
{user.name}
</button>
</li>
{/each}
</ul>
</div>
<style>
.root {
padding-bottom: 10px;
font-size: 0.9rem;
color: var(--secondary50);
font-weight: bold;
position: relative;
padding-left: 1.8rem;
}
ul {
margin: 0;
padding: 0;
list-style: none;
}
li {
margin: 0.5rem 0;
}
button {
margin: 0 0 0 6px;
padding: 0;
border: none;
font-size: 13px;
outline: none;
cursor: pointer;
background: rgba(0, 0, 0, 0);
}
.active {
font-weight: 500;
}
.icon {
display: inline-block;
width: 14px;
color: #333;
}
</style>

View File

@ -13,35 +13,26 @@
<style>
.apps-card {
background-color: var(--white);
padding: 20px 20px 20px 20px;
max-width: 400px;
padding: var(--spacing-xl);
max-width: 300px;
max-height: 150px;
border-radius: 5px;
border: 1px solid var(--grey-4);
font-family: Inter;
border-radius: var(--border-radius-m);
border: var(--border-dark);
}
.app-button:hover {
background-color: var(--grey-1);
background-color: var(--white);
color: var(--black);
text-decoration: none;
}
.app-title {
font-size: 18px;
font-size: var(--font-size-l);
font-weight: 600;
color: var(--ink);
text-transform: capitalize;
}
.app-desc {
color: var(--grey-7);
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.card-footer {
display: flex;
flex-direction: row;
@ -52,17 +43,18 @@
.app-button {
align-items: center;
display: flex;
background-color: var(--white);
color: var(--ink);
background-color: var(--ink);
color: var(--white);
border: 1.5px var(--ink) solid;
width: 100%;
justify-content: center;
padding: 12px 20px;
border-radius: 5px;
border: 1px var(--grey-2) solid;
font-size: 14px;
font-weight: 400;
padding: 8px 16px;
border-radius: var(--border-radius-s);
font-size: var(--font-size-xs);
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
box-sizing: border-box;
font-family: var(--font-sans);
}
</style>

View File

@ -25,8 +25,8 @@
<style>
.apps {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
grid-gap: 20px 40px;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
grid-gap: var(--layout-m);
justify-content: start;
}

View File

@ -208,16 +208,17 @@
</div>
<div class="footer">
{#if $createAppStore.currentStep > 0}
<Button secondary on:click={back}>Back</Button>
<Button medium secondary on:click={back}>Back</Button>
{/if}
{#if $createAppStore.currentStep < steps.length - 1}
<Button secondary on:click={next} disabled={!currentStepIsValid}>
<Button medium blue on:click={next} disabled={!currentStepIsValid}>
Next
</Button>
{/if}
{#if $createAppStore.currentStep === steps.length - 1}
<Button
secondary
medium
blue
on:click={signUp}
disabled={!fullFormIsValid || submitting}>
{submitting ? 'Loading...' : 'Submit'}

View File

@ -5,7 +5,7 @@
let blurred = { appName: false }
</script>
<h2>Create your first web app</h2>
<h2>Create your web app</h2>
<div class="container">
<Input
on:input={() => (blurred.appName = true)}

View File

@ -21,7 +21,7 @@
placeholder="Password"
type="pasword"
error={blurred.password && validationErrors.password} />
<Select name="accessLevelId">
<Select secondary name="accessLevelId">
<option value="ADMIN">Admin</option>
<option value="POWER_USER">Power User</option>
</Select>

View File

@ -1,111 +0,0 @@
<script>
import { store } from "builderStore"
import UIkit from "uikit"
import ActionButton from "components/common/ActionButton.svelte"
import ButtonGroup from "components/common/ButtonGroup.svelte"
import CodeMirror from "codemirror"
import "codemirror/mode/javascript/javascript.js"
export let onCodeChanged
export let code
export const show = () => {
UIkit.modal(codeModal).show()
}
let codeModal
let editor
let cmInstance
$: currentCode = code
$: originalCode = code
$: {
if (editor) {
if (!cmInstance) {
cmInstance = CodeMirror.fromTextArea(editor, {
mode: "javascript",
lineNumbers: false,
lineWrapping: true,
smartIndent: true,
matchBrackets: true,
readOnly: false,
})
cmInstance.on("change", () => (currentCode = cmInstance.getValue()))
}
cmInstance.focus()
cmInstance.setValue(code || "")
}
}
const cancel = () => {
UIkit.modal(codeModal).hide()
currentCode = originalCode
}
const save = () => {
originalCode = currentCode
onCodeChanged(currentCode)
UIkit.modal(codeModal).hide()
}
</script>
<div bind:this={codeModal} uk-modal>
<div class="uk-modal-dialog" uk-overflow-auto>
<div class="uk-modal-header">
<h3>Code</h3>
</div>
<div class="uk-modal-body uk-form-horizontal">
<p>
Use the code box below to control how this component is displayed, with
javascript.
</p>
<div>
<div class="editor-code-surround">
function(render, context, state, route) {'{'}
</div>
<div class="editor">
<textarea bind:this={editor} />
</div>
<div class="editor-code-surround">{'}'}</div>
</div>
</div>
<div class="uk-modal-footer">
<ButtonGroup>
<ActionButton primary on:click={save}>Save</ActionButton>
<ActionButton alert on:click={cancel}>Close</ActionButton>
</ButtonGroup>
</div>
</div>
</div>
<style>
h3 {
text-transform: uppercase;
font-size: 13px;
font-weight: 700;
color: #8997ab;
margin-bottom: 10px;
}
p {
font-size: 13px;
color: #333;
margin-top: 0;
}
.editor {
border-style: dotted;
border-width: 1px;
border-color: gainsboro;
padding: 10px 30px;
}
.editor-code-surround {
font-family: "Courier New", Courier, monospace;
}
</style>

View File

@ -1,320 +0,0 @@
<script>
import { onMount, createEventDispatcher } from "svelte"
import { fade } from "svelte/transition"
import Swatch from "./Swatch.svelte"
import CheckedBackground from "./CheckedBackground.svelte"
import { buildStyle } from "./helpers.js"
import {
getColorFormat,
convertToHSVA,
convertHsvaToFormat,
} from "./utils.js"
import Slider from "./Slider.svelte"
import Palette from "./Palette.svelte"
import ButtonGroup from "./ButtonGroup.svelte"
import Input from "./Input.svelte"
import Portal from "./Portal.svelte"
export let value = "#3ec1d3ff"
export let open = false
export let swatches = [] //TODO: Safe swatches - limit to 12. warn in console
export let disableSwatches = false
export let format = "hexa"
export let style = ""
export let pickerHeight = 0
export let pickerWidth = 0
let colorPicker = null
let adder = null
let h = null
let s = null
let v = null
let a = null
const dispatch = createEventDispatcher()
onMount(() => {
if (!swatches.length > 0) {
//Don't use locally stored recent colors if swatches have been passed as props
getRecentColors()
}
if (colorPicker) {
colorPicker.focus()
}
if (format) {
convertAndSetHSVA()
}
})
function getRecentColors() {
let colorStore = localStorage.getItem("cp:recent-colors")
if (colorStore) {
swatches = JSON.parse(colorStore)
}
}
function handleEscape(e) {
if (open && e.key === "Escape") {
open = false
}
}
function setRecentColor(color) {
if (swatches.length === 12) {
swatches.splice(0, 1)
}
if (!swatches.includes(color)) {
swatches = [...swatches, color]
localStorage.setItem("cp:recent-colors", JSON.stringify(swatches))
}
}
function convertAndSetHSVA() {
let hsva = convertToHSVA(value, format)
setHSVA(hsva)
}
function setHSVA([hue, sat, val, alpha]) {
h = hue
s = sat
v = val
a = alpha
}
//fired by choosing a color from the palette
function setSaturationAndValue({ detail }) {
s = detail.s
v = detail.v
value = convertHsvaToFormat([h, s, v, a], format)
dispatchValue()
}
function setHue({ color, isDrag }) {
h = color
value = convertHsvaToFormat([h, s, v, a], format)
if (!isDrag) {
dispatchValue()
}
}
function setAlpha({ color, isDrag }) {
a = color === "1.00" ? "1" : color
value = convertHsvaToFormat([h, s, v, a], format)
if (!isDrag) {
dispatchValue()
}
}
function dispatchValue() {
dispatch("change", value)
}
function changeFormatAndConvert(f) {
format = f
value = convertHsvaToFormat([h, s, v, a], format)
}
function handleColorInput(text) {
let format = getColorFormat(text)
if (format) {
value = text
convertAndSetHSVA()
}
}
function dispatchInputChange() {
if (format) {
dispatchValue()
}
}
function addSwatch() {
if (format) {
dispatch("addswatch", value)
setRecentColor(value)
}
}
function removeSwatch(idx) {
let removedSwatch = swatches.splice(idx, 1)
swatches = swatches
dispatch("removeswatch", removedSwatch)
localStorage.setItem("cp:recent-colors", JSON.stringify(swatches))
}
function applySwatch(color) {
if (value !== color) {
format = getColorFormat(color)
if (format) {
value = color
convertAndSetHSVA()
dispatchValue()
}
}
}
$: border = v > 90 && s < 5 ? "1px dashed #dedada" : ""
$: selectedColorStyle = buildStyle({ background: value, border })
$: shrink = swatches.length > 0
</script>
<Portal>
<div
class="colorpicker-container"
transition:fade
bind:this={colorPicker}
{style}
tabindex="0"
on:keydown={handleEscape}
bind:clientHeight={pickerHeight}
bind:clientWidth={pickerWidth}>
<div class="palette-panel">
<Palette on:change={setSaturationAndValue} {h} {s} {v} {a} />
</div>
<div class="control-panel">
<div class="alpha-hue-panel">
<div>
<CheckedBackground borderRadius="50%" backgroundSize="8px">
<div class="selected-color" style={selectedColorStyle} />
</CheckedBackground>
</div>
<div>
<Slider
type="hue"
value={h}
on:change={hue => setHue(hue.detail)}
on:dragend={dispatchValue} />
<CheckedBackground borderRadius="10px" backgroundSize="7px">
<Slider
type="alpha"
value={a}
on:change={(alpha, isDrag) => setAlpha(alpha.detail, isDrag)}
on:dragend={dispatchValue} />
</CheckedBackground>
</div>
</div>
{#if !disableSwatches}
<div transition:fade class="swatch-panel">
{#if swatches.length > 0}
{#each swatches as color, idx}
<Swatch
{color}
on:click={() => applySwatch(color)}
on:removeswatch={() => removeSwatch(idx)} />
{/each}
{/if}
{#if swatches.length !== 12}
<div
bind:this={adder}
transition:fade
class="adder"
on:click={addSwatch}
class:shrink>
<span>&plus;</span>
</div>
{/if}
</div>
{/if}
<div class="format-input-panel">
<ButtonGroup {format} onclick={changeFormatAndConvert} />
<Input
{value}
on:input={event => handleColorInput(event.target.value)}
on:change={dispatchInputChange} />
</div>
</div>
</div>
</Portal>
<style>
.colorpicker-container {
position: absolute;
outline: none;
z-index: 3;
display: flex;
font-size: 11px;
font-weight: 400;
flex-direction: column;
margin: 5px 0px;
height: auto;
width: 220px;
background: #ffffff;
border-radius: 2px;
box-shadow: 0 0.15em 1.5em 0 rgba(0, 0, 0, 0.1),
0 0 1em 0 rgba(0, 0, 0, 0.03);
}
.palette-panel {
flex: 1;
}
.control-panel {
flex: 1;
display: flex;
flex-direction: column;
padding: 8px;
background: white;
border: 1px solid #d2d2d2;
color: #777373;
}
.alpha-hue-panel {
display: grid;
grid-template-columns: 25px 1fr;
grid-gap: 15px;
justify-content: center;
align-items: center;
}
.selected-color {
width: 30px;
height: 30px;
border-radius: 50%;
}
.swatch-panel {
flex: 0 0 15px;
display: flex;
flex-flow: row wrap;
justify-content: flex-start;
padding: 0 5px;
max-height: 56px;
}
.adder {
flex: 1;
height: 20px;
display: flex;
transition: flex 0.5s;
justify-content: center;
align-items: center;
background: #f1f3f4;
cursor: pointer;
border: 1px solid #d4d4d4;
border-radius: 8px;
margin-left: 5px;
margin-top: 3px;
font-weight: 500;
}
.shrink {
flex: 0 0 20px;
}
.format-input-panel {
display: flex;
flex-direction: column;
justify-content: center;
padding-top: 3px;
}
</style>

View File

@ -1,157 +0,0 @@
<script>
import Colorpicker from "./Colorpicker.svelte"
import CheckedBackground from "./CheckedBackground.svelte"
import { createEventDispatcher, beforeUpdate } from "svelte"
import { buildStyle } from "./helpers.js"
import { fade } from "svelte/transition"
import { getColorFormat } from "./utils.js"
export let value = "#3ec1d3ff"
export let swatches = []
export let disableSwatches = false
export let open = false
export let width = "25px"
export let height = "25px"
let format = "hexa"
let dimensions = { top: 0, bottom: 0, right: 0, left: 0 }
let positionSide = "top"
let colorPreview = null
let previewHeight = null
let previewWidth = null
let pickerWidth = 0
let pickerHeight = 0
let errorMsg = null
const dispatch = createEventDispatcher()
beforeUpdate(() => {
format = getColorFormat(value)
if (!format) {
errorMsg = `Colorpicker - ${value} is an unknown color format. Please use a hex, rgb or hsl value`
console.error(errorMsg)
} else {
errorMsg = null
}
})
function openColorpicker(event) {
if (colorPreview) {
open = true
}
}
function onColorChange(color) {
value = color.detail
dispatch("change", color.detail)
}
$: if (open && colorPreview) {
const {
top: spaceAbove,
width,
bottom,
right,
left,
} = colorPreview.getBoundingClientRect()
const spaceBelow = window.innerHeight - bottom
const previewCenter = previewWidth / 2
let y, x
if (spaceAbove > spaceBelow) {
positionSide = "bottom"
y = window.innerHeight - spaceAbove
} else {
positionSide = "top"
y = bottom
}
x = left + previewCenter - pickerWidth / 2
dimensions = { [positionSide]: y.toFixed(1), left: x.toFixed(1) }
}
$: previewStyle = buildStyle({ width, height, background: value })
$: errorPreviewStyle = buildStyle({ width, height })
$: pickerStyle = buildStyle({
[positionSide]: `${dimensions[positionSide]}px`,
left: `${dimensions.left}px`,
})
</script>
<div class="color-preview-container">
{#if !errorMsg}
<CheckedBackground borderRadius="3px" backgroundSize="8px">
<div
bind:this={colorPreview}
bind:clientHeight={previewHeight}
bind:clientWidth={previewWidth}
class="color-preview"
style={previewStyle}
on:click={openColorpicker} />
</CheckedBackground>
{#if open}
<Colorpicker
style={pickerStyle}
on:change={onColorChange}
on:addswatch
on:removeswatch
bind:format
bind:value
bind:pickerHeight
bind:pickerWidth
bind:open
{swatches}
{disableSwatches} />
<div on:click|self={() => (open = false)} class="overlay" />
{/if}
{:else}
<div class="color-preview preview-error" style={errorPreviewStyle}>
<span>&times;</span>
</div>
{/if}
</div>
<style>
.color-preview-container {
display: flex;
flex-flow: row nowrap;
height: fit-content;
}
.color-preview {
cursor: pointer;
border-radius: 3px;
border: 1px solid #dedada;
}
.preview-error {
background: #cccccc;
color: #808080;
text-align: center;
font-size: 18px;
cursor: not-allowed;
}
/* .picker-container {
position: absolute;
z-index: 3;
width: fit-content;
height: fit-content;
} */
.overlay {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
z-index: 2;
}
</style>

View File

@ -1,37 +0,0 @@
<script>
import { onMount } from "svelte"
export let target = document.body
let targetEl
let portal
let componentInstance
onMount(() => {
if (typeof target === "string") {
targetEl = document.querySelector(target)
// Force exit
if (targetEl === null) {
return () => {}
}
} else if (target instanceof HTMLElement) {
targetEl = target
} else {
throw new TypeError(
`Unknown target type: ${typeof target}. Allowed types: String (CSS selector), HTMLElement.`
)
}
portal = document.createElement("div")
targetEl.appendChild(portal)
portal.appendChild(componentInstance)
return () => {
targetEl.removeChild(portal)
}
})
</script>
<div bind:this={componentInstance}>
<slot />
</div>

View File

@ -28,8 +28,7 @@
})
$: dropdown && UIkit.util.on(dropdown, "shown", () => (hidden = false))
$: noChildrenAllowed =
!component ||
getComponentDefinition($store, component._component).children === false
!component || !getComponentDefinition($store, component._component).children
$: noPaste = !$store.componentToPaste
const lastPartOfName = c => (c ? last(c._component.split("/")) : "")
@ -105,49 +104,14 @@
})
}
const generateNewIdsForComponent = c =>
walkProps(c, p => {
p._id = uuid()
})
const storeComponentForCopy = (cut = false) => {
store.update(s => {
const copiedComponent = cloneDeep(component)
s.componentToPaste = copiedComponent
if (cut) {
const parent = getParent(s.currentPreviewItem.props, component._id)
parent._children = parent._children.filter(c => c._id !== component._id)
selectComponent(s, parent)
}
return s
})
// lives in store - also used by drag drop
store.storeComponentForCopy(component, cut)
}
const pasteComponent = mode => {
store.update(s => {
if (!s.componentToPaste) return s
const componentToPaste = cloneDeep(s.componentToPaste)
generateNewIdsForComponent(componentToPaste)
delete componentToPaste._cutId
if (mode === "inside") {
component._children.push(componentToPaste)
return s
}
const parent = getParent(s.currentPreviewItem.props, component)
const targetIndex = parent._children.indexOf(component)
const index = mode === "above" ? targetIndex : targetIndex + 1
parent._children.splice(index, 0, cloneDeep(componentToPaste))
regenerateCssForCurrentScreen(s)
saveCurrentPreviewItem(s)
selectComponent(s, componentToPaste)
return s
})
// lives in store - also used by drag drop
store.pasteComponent(component, mode)
}
</script>

View File

@ -1,6 +1,5 @@
<script>
import { setContext, onMount } from "svelte"
import PropsView from "./PropsView.svelte"
import { store } from "builderStore"
import IconButton from "components/common/IconButton.svelte"
@ -11,8 +10,6 @@
CircleIndicator,
EventsIcon,
} from "components/common/Icons/"
import CodeEditor from "./CodeEditor.svelte"
import LayoutEditor from "./LayoutEditor.svelte"
import EventsEditor from "./EventsEditor"
import panelStructure from "./temporaryPanelStructure.js"
import CategoryTab from "./CategoryTab.svelte"

View File

@ -1,59 +0,0 @@
<script>
import { searchAllComponents } from "./pagesParsing/searchComponents"
import { store } from "../builderStore"
export let onComponentChosen = () => {}
let phrase = ""
components = $store.components
$: filteredComponents = !phrase ? [] : searchAllComponents(components, phrase)
</script>
<div class="root">
<form on:submit|preventDefault class="uk-search uk-search-large">
<span uk-search-icon />
<input
class="uk-search-input"
type="search"
placeholder="Based on component..."
bind:value={phrase} />
</form>
<div>
{#each filteredComponents as component}
<div class="component" on:click={() => onComponentChosen(component)}>
<div class="title">{component.name}</div>
<div class="description">{component.description}</div>
</div>
{/each}
</div>
</div>
<style>
.component {
padding: 5px;
border-style: solid;
border-width: 0 0 1px 0;
border-color: var(--grey-1);
cursor: pointer;
}
.component:hover {
background-color: var(--primary10);
}
.component > .title {
font-size: 13pt;
color: var(--ink);
}
.component > .description {
font-size: 10pt;
color: var(--blue);
font-style: italic;
}
</style>

View File

@ -7,9 +7,12 @@
import { store } from "builderStore"
import { ArrowDownIcon, ShapeIcon } from "components/common/Icons/"
import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte"
import { writable } from "svelte/store"
export let screens = []
const dragDropStore = writable({})
let confirmDeleteDialog
let componentToDelete = ""
@ -57,7 +60,8 @@
{#if $store.currentPreviewItem.props._instanceName && $store.currentPreviewItem.props._instanceName === screen.props._instanceName && screen.props._children}
<ComponentsHierarchyChildren
components={screen.props._children}
currentComponent={$store.currentComponentInfo} />
currentComponent={$store.currentComponentInfo}
{dragDropStore} />
{/if}
{/each}
@ -94,10 +98,6 @@
margin: 0 0 0 5px;
}
:global(svg) {
transition: 0.2s;
}
.rotate :global(svg) {
transform: rotate(-90deg);
}

View File

@ -15,6 +15,10 @@
export let currentComponent
export let onSelect = () => {}
export let level = 0
export let dragDropStore
let dropUnderComponent
let componentToDrop
const capitalise = s => s.substring(0, 1).toUpperCase() + s.substring(1)
const get_name = s => (!s ? "" : last(s.split("/")))
@ -32,15 +36,92 @@
// Go to correct URL
$goto(`./:page/:screen/${path}`)
}
const dragstart = component => e => {
e.dataTransfer.dropEffect = "move"
dragDropStore.update(s => {
s.componentToDrop = component
return s
})
}
const dragover = (component, index) => e => {
const canHaveChildrenButIsEmpty =
$store.components[component._component].children &&
component._children.length === 0
e.dataTransfer.dropEffect = "copy"
dragDropStore.update(s => {
const isBottomHalf = e.offsetY > e.currentTarget.offsetHeight / 2
s.targetComponent = component
// only allow dropping inside when container type
// is empty. If it has children, the user can drag over
// it's existing children
if (canHaveChildrenButIsEmpty) {
if (index === 0) {
// when its the first component in the screen,
// we divide into 3, so we can paste above, inside or below
const pos = e.offsetY / e.currentTarget.offsetHeight
if (pos < 0.4) {
s.dropPosition = "above"
} else if (pos > 0.8) {
// purposely giving this the least space as it is often covered
// by the component below's "above" space
s.dropPosition = "below"
} else {
s.dropPosition = "inside"
}
} else {
s.dropPosition = isBottomHalf ? "below" : "inside"
}
} else {
s.dropPosition = isBottomHalf ? "below" : "above"
}
return s
})
return false
}
const drop = () => {
if ($dragDropStore.targetComponent !== $dragDropStore.componentToDrop) {
store.storeComponentForCopy($dragDropStore.componentToDrop, true)
store.pasteComponent(
$dragDropStore.targetComponent,
$dragDropStore.dropPosition
)
}
dragDropStore.update(s => {
s.dropPosition = ""
s.targetComponent = null
s.componentToDrop = null
return s
})
}
</script>
<ul>
{#each components as component, index (component._id)}
<li on:click|stopPropagation={() => selectComponent(component)}>
{#if $dragDropStore && $dragDropStore.targetComponent === component && $dragDropStore.dropPosition === 'above'}
<div
on:drop={drop}
ondragover="return false"
ondragenter="return false"
class="budibase__nav-item item drop-item"
style="margin-left: {level * 20 + 40}px" />
{/if}
<div
class="budibase__nav-item item"
class:selected={currentComponent === component}
style="padding-left: {level * 20 + 40}px">
style="padding-left: {level * 20 + 40}px"
draggable={true}
on:dragstart={dragstart(component)}
on:dragover={dragover(component, index)}
on:drop={drop}
ondragover="return false"
ondragenter="return false">
<div class="nav-item">
<i class="icon ri-arrow-right-circle-line" />
{isScreenslot(component._component) ? 'Screenslot' : component._instanceName}
@ -55,8 +136,18 @@
components={component._children}
{currentComponent}
{onSelect}
{dragDropStore}
level={level + 1} />
{/if}
{#if $dragDropStore && $dragDropStore.targetComponent === component && ($dragDropStore.dropPosition === 'inside' || $dragDropStore.dropPosition === 'below')}
<div
on:drop={drop}
ondragover="return false"
ondragenter="return false"
class="budibase__nav-item item drop-item"
style="margin-left: {(level + ($dragDropStore.dropPosition === 'inside' ? 2 : 0)) * 20 + 40}px" />
{/if}
</li>
{/each}
</ul>
@ -78,6 +169,11 @@
align-items: center;
}
.drop-item {
background: var(--blue-light);
height: 36px;
}
.actions {
display: none;
height: 24px;

View File

@ -1,90 +0,0 @@
<script>
import PropsView from "./PropsView.svelte"
import { store } from "../builderStore"
import Textbox from "../common/Textbox.svelte"
import Button from "../common/Button.svelte"
import { LayoutIcon, PaintIcon, TerminalIcon } from "../common/Icons/"
import { cloneDeep, join, split, last } from "lodash/fp"
import { assign } from "lodash"
$: component = $store.currentPreviewItem
$: componentInfo = $store.currentComponentInfo
$: components = $store.components
const updateComponent = doChange => doChange(cloneDeep(component))
const onPropsChanged = newProps => {
updateComponent(newComponent => assign(newComponent.props, newProps))
}
</script>
<div class="root">
<ul>
<li>
<button>
<PaintIcon />
</button>
</li>
<li>
<button>
<LayoutIcon />
</button>
</li>
<li>
<button>
<TerminalIcon />
</button>
</li>
</ul>
<div class="component-props-container">
<PropsView {componentInfo} {onPropsChanged} />
</div>
</div>
<style>
.root {
height: 100%;
display: flex;
flex-direction: column;
}
.title > div:nth-child(1) {
grid-column-start: name;
color: var(--ink);
}
.title > div:nth-child(2) {
grid-column-start: actions;
}
.component-props-container {
flex: 1 1 auto;
overflow-y: auto;
}
ul {
list-style: none;
display: flex;
padding: 0;
}
li {
margin-right: 20px;
background: none;
border-radius: 5px;
width: 45px;
height: 45px;
}
li button {
width: 100%;
height: 100%;
background: none;
border: none;
border-radius: 5px;
padding: 13px;
}
</style>

View File

@ -1,13 +1,10 @@
<script>
import { store } from "builderStore"
import { Button } from "@budibase/bbui"
import { Button, Select } from "@budibase/bbui"
import Modal from "../../common/Modal.svelte"
import HandlerSelector from "./HandlerSelector.svelte"
import IconButton from "../../common/IconButton.svelte"
import ActionButton from "../../common/ActionButton.svelte"
import PlusButton from "../../common/PlusButton.svelte"
import Select from "../../common/Select.svelte"
import Input from "../../common/Input.svelte"
import getIcon from "../../common/icon"
import { CloseIcon } from "components/common/Icons/"

View File

@ -13,9 +13,6 @@
} from "lodash/fp"
import { pipe } from "components/common/core"
import Checkbox from "components/common/Checkbox.svelte"
import Textbox from "components/common/Textbox.svelte"
import Dropdown from "components/common/Dropdown.svelte"
import PlusButton from "components/common/PlusButton.svelte"
import IconButton from "components/common/IconButton.svelte"
import EventEditorModal from "./EventEditorModal.svelte"

View File

@ -1,8 +1,6 @@
<script>
import { Button } from "@budibase/bbui"
import { Button, Select } from "@budibase/bbui"
import IconButton from "components/common/IconButton.svelte"
import PlusButton from "components/common/PlusButton.svelte"
import Select from "components/common/Select.svelte"
import StateBindingCascader from "./StateBindingCascader.svelte"
import { find, map, keys, reduce, keyBy } from "lodash/fp"
import { pipe } from "components/common/core"

View File

@ -1,8 +1,6 @@
<script>
import { Input } from "@budibase/bbui"
import { Input, Select } from "@budibase/bbui"
import IconButton from "components/common/IconButton.svelte"
import PlusButton from "components/common/PlusButton.svelte"
import Select from "components/common/Select.svelte"
import { find, map, keys, reduce, keyBy } from "lodash/fp"
import { pipe } from "components/common/core"
import { EVENT_TYPE_MEMBER_NAME } from "components/common/eventHandlers"

View File

@ -1,170 +0,0 @@
<script>
import InputGroup from "../common/Inputs/InputGroup.svelte"
import LayoutTemplateControls from "./LayoutTemplateControls.svelte"
export let onStyleChanged = () => {}
export let component
const tbrl = [
{ placeholder: "T" },
{ placeholder: "R" },
{ placeholder: "B" },
{ placeholder: "L" },
]
const se = [{ placeholder: "START" }, { placeholder: "END" }]
const single = [{ placeholder: "" }]
$: layout = {
...component._styles.position,
...component._styles.layout,
}
$: layouts = {
templaterows: ["Grid Rows", single],
templatecolumns: ["Grid Columns", single],
}
$: display = {
direction: ["Direction", single],
align: ["Align", single],
justify: ["Justify", single],
}
$: positions = {
column: ["Column", se],
row: ["Row", se],
}
$: spacing = {
margin: ["Margin", tbrl, "small"],
padding: ["Padding", tbrl, "small"],
}
$: size = {
height: ["Height", single],
width: ["Width", single],
}
$: zindex = {
zindex: ["Z-Index", single],
}
const newValue = n => Array(n).fill("")
</script>
<h3>Layout</h3>
<div class="layout-pos">
{#each Object.entries(display) as [key, [name, meta, size]] (component._id + key)}
<div class="grid">
<h5>{name}:</h5>
<LayoutTemplateControls
onStyleChanged={_value => onStyleChanged('layout', key, _value)}
values={layout[key] || newValue(meta.length)}
propertyName={name}
{meta}
{size}
type="text" />
</div>
{/each}
</div>
<!-- <h4>Positioning</h4>
<div class="layout-pos">
{#each Object.entries(positions) as [key, [name, meta, size]] (component._id + key)}
<div class="grid">
<h5>{name}:</h5>
<InputGroup
onStyleChanged={_value => onStyleChanged('position', key, _value)}
values={layout[key] || newValue(meta.length)}
{meta}
{size} />
</div>
{/each}
</div> -->
<h3>Spacing</h3>
<div class="layout-spacing">
{#each Object.entries(spacing) as [key, [name, meta, size]] (component._id + key)}
<div class="grid">
<h5>{name}:</h5>
<InputGroup
onStyleChanged={_value => onStyleChanged('position', key, _value)}
values={layout[key] || newValue(meta.length)}
{meta}
{size}
type="text" />
</div>
{/each}
</div>
<h3>Size</h3>
<div class="layout-layer">
{#each Object.entries(size) as [key, [name, meta, size]] (component._id + key)}
<div class="grid">
<h5>{name}:</h5>
<InputGroup
onStyleChanged={_value => onStyleChanged('position', key, _value)}
values={layout[key] || newValue(meta.length)}
type="text"
{meta}
{size} />
</div>
{/each}
</div>
<h3>Order</h3>
<div class="layout-layer">
{#each Object.entries(zindex) as [key, [name, meta, size]] (component._id + key)}
<div class="grid">
<h5>{name}:</h5>
<InputGroup
onStyleChanged={_value => onStyleChanged('position', key, _value)}
values={layout[key] || newValue(meta.length)}
{meta}
{size} />
</div>
{/each}
</div>
<style>
h3 {
text-transform: uppercase;
font-size: 13px;
font-weight: 600;
color: var(--ink);
opacity: 0.6;
margin-bottom: 10px;
}
h4 {
text-transform: uppercase;
font-size: 10px;
font-weight: 600;
color: var(--ink);
opacity: 0.4;
margin-bottom: 10px;
}
h5 {
font-size: 13px;
font-weight: 400;
color: var(--ink);
opacity: 0.8;
padding-top: 13px;
margin-bottom: 0;
}
div > div {
display: grid;
grid-template-rows: 1fr;
grid-gap: 10px;
height: 40px;
margin-bottom: 15px;
}
.grid {
grid-template-columns: 70px 2fr;
}
</style>

View File

@ -1,6 +1,4 @@
<script>
import PlusButton from "../common/PlusButton.svelte"
export let meta = []
export let size = ""
export let values = []

View File

@ -1,7 +1,5 @@
<script>
import { store } from "builderStore"
import PropsView from "./PropsView.svelte"
import Textbox from "components/common/Textbox.svelte"
import Button from "components/common/Button.svelte"
import ActionButton from "components/common/ActionButton.svelte"
import ButtonGroup from "components/common/ButtonGroup.svelte"
@ -10,6 +8,7 @@
import UIkit from "uikit"
import { isRootComponent } from "./pagesParsing/searchComponents"
import { splitName } from "./pagesParsing/splitRootComponentName.js"
import { Input, Select } from "@budibase/bbui"
import { find, filter, some, map, includes } from "lodash/fp"
import { assign } from "lodash"
@ -23,8 +22,7 @@
let layoutComponent
let screens
let name = ""
let saveAttempted = false
let routeError
$: layoutComponents = Object.values($store.components).filter(
componentDefinition => componentDefinition.container
@ -39,18 +37,21 @@
$: route = !route && $store.screens.length === 0 ? "*" : route
const save = () => {
saveAttempted = true
if (!route) {
routeError = "Url is required"
} else {
if (routeNameExists(route)) {
routeError = "This url is already taken"
} else {
routeError = ""
}
}
const isValid =
name.length > 0 &&
!screenNameExists(name) &&
route.length > 0 &&
!routeNameExists(route) &&
layoutComponent
if (!isValid) return
if (routeError) return false
store.createScreen(name, route, layoutComponent._component)
name = ""
route = ""
dialog.hide()
}
@ -58,12 +59,6 @@
dialog.hide()
}
const screenNameExists = name => {
return $store.screens.some(
screen => screen.name.toLowerCase() === name.toLowerCase()
)
}
const routeNameExists = route => {
return $store.screens.some(
screen => screen.route.toLowerCase() === route.toLowerCase()
@ -84,40 +79,26 @@
onOk={save}
okText="Create Screen">
<div class="uk-form-horizontal">
<div data-cy="new-screen-dialog">
<div class="uk-margin">
<label class="uk-form-label">Name</label>
<div class="uk-form-controls">
<input
class="uk-input uk-form-small"
class:uk-form-danger={saveAttempted && (name.length === 0 || screenNameExists(name))}
bind:value={name} />
</div>
<Input label="Name" bind:value={name} />
</div>
<div class="uk-margin">
<label class="uk-form-label">Route (URL)</label>
<div class="uk-form-controls">
<input
class="uk-input uk-form-small"
class:uk-form-danger={saveAttempted && (route.length === 0 || routeNameExists(route))}
bind:value={route}
on:change={routeChanged} />
</div>
<Input
label="Url"
error={routeError}
bind:value={route}
on:change={routeChanged} />
</div>
<div class="uk-margin">
<label class="uk-form-label">Layout Component</label>
<div class="uk-form-controls">
<select
class="uk-select uk-form-small"
bind:value={layoutComponent}
class:uk-form-danger={saveAttempted && !layoutComponent}>
{#each layoutComponents as { _component, name }}
<option value={_component}>{name}</option>
{/each}
</select>
</div>
<label>Layout Component</label>
<Select bind:value={layoutComponent} secondary>
{#each layoutComponents as { _component, name }}
<option value={_component}>{name}</option>
{/each}
</Select>
</div>
</div>
@ -128,28 +109,4 @@
display: flex;
flex-direction: column;
}
.uk-form-controls {
margin-left: 0 !important;
}
.uk-form-label {
padding-bottom: 10px;
font-weight: 500;
font-size: 16px;
color: var(--grey-7);
}
.uk-input {
height: 40px !important;
border-radius: 3px;
}
.uk-select {
height: 40px !important;
font-weight: 500px;
color: var(--grey-5);
border: 1px solid var(--grey-2);
border-radius: 3px;
}
</style>

View File

@ -1,33 +0,0 @@
<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 {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>

View File

@ -16,12 +16,14 @@
import { pipe } from "components/common/core"
import { store } from "builderStore"
import { ArrowDownIcon, GridIcon } from "components/common/Icons/"
import { writable } from "svelte/store"
export let layout
let confirmDeleteDialog
let componentToDelete = ""
const dragDropStore = writable({})
const joinPath = join("/")
const lastPartOfName = c =>
@ -57,7 +59,8 @@
<ComponentsHierarchyChildren
thisComponent={_layout.component.props}
components={_layout.component.props._children}
currentComponent={$store.currentComponentInfo} />
currentComponent={$store.currentComponentInfo}
{dragDropStore} />
{/if}
<style>
@ -81,10 +84,6 @@
color: var(--grey-7);
}
:global(svg) {
transition: 0.2s;
}
.rotate :global(svg) {
transform: rotate(-90deg);
}

View File

@ -1,65 +0,0 @@
<script>
import Textbox from "components/common/Textbox.svelte"
import Dropdown from "components/common/Dropdown.svelte"
import Button from "components/common/Button.svelte"
import { store } from "builderStore"
import { isRootComponent } from "./pagesParsing/searchComponents"
import { pipe } from "components/common/core"
import { filter, find, concat } from "lodash/fp"
const notSelectedComponent = { name: "(none selected)" }
$: page = $store.pages[$store.currentPageName]
$: title = page.index.title
$: components = pipe($store.components, [
filter(store => !isRootComponent($store)),
concat([notSelectedComponent]),
])
$: entryComponent = components[page.appBody] || notSelectedComponent
const save = () => {
if (!title || !entryComponent || entryComponent === notSeletedComponent)
return
const page = {
index: {
title,
},
appBody: entryComponent.name,
}
store.savePage(page)
}
</script>
<div class="root">
<h3>{$store.currentPageName}</h3>
<form on:submit|preventDefault class="uk-form-horizontal">
<Textbox bind:text={title} label="Title" hasError={!title} />
<div class="help-text">
The title of your page, displayed in the bowser tab
</div>
<Dropdown
label="App Entry Component"
options={components}
bind:selected={entryComponent}
textMember={v => v.name} />
<div class="help-text">
The component that will be loaded into the body of the page
</div>
<div style="margin-top: 20px" />
<Button on:click={save}>Save</Button>
</form>
</div>
<style>
.root {
padding: 15px;
}
.help-text {
color: var(--grey-2);
font-size: 10pt;
}
</style>

View File

@ -1,46 +0,0 @@
<script>
import { store } from "builderStore"
import Checkbox from "../common/Checkbox.svelte"
import Textbox from "../common/Textbox.svelte"
import Dropdown from "../common/Dropdown.svelte"
import StateBindingControl from "./StateBindingControl.svelte"
export let index
export let prop_name
export let prop_value
export let prop_definition = {}
</script>
<div class="root">
{#if prop_definition.type !== 'event'}
<h5>{prop_name}</h5>
<StateBindingControl
value={prop_value}
type={prop_definition.type || prop_definition}
options={prop_definition.options}
styleBindingProperty={prop_definition.styleBindingProperty}
onChanged={v => store.setComponentProp(prop_name, v)} />
{/if}
</div>
<style>
.root {
height: 40px;
margin-bottom: 15px;
display: grid;
grid-template-rows: 1fr;
grid-template-columns: 70px 1fr;
grid-gap: 10px;
align-items: baseline;
}
h5 {
word-wrap: break-word;
font-size: 13px;
font-weight: 400;
color: var(--ink);
opacity: 0.8;
padding-top: 13px;
margin-bottom: 0;
}
</style>

View File

@ -0,0 +1,11 @@
<script>
/*
This file exists because of how we pass this control to the
properties panel - via a JS reference, not using svelte tags
... checkout the use of Input in propertyCategories to see what i mean
*/
import { Input } from "@budibase/bbui"
export let name, value, placeholder, type
</script>
<Input {name} {value} {placeholder} {type} thin on:change />

View File

@ -1,54 +0,0 @@
<script>
import { some, includes, filter } from "lodash/fp"
import Textbox from "../common/Textbox.svelte"
import Dropdown from "../common/Dropdown.svelte"
import PropControl from "./PropControl.svelte"
import IconButton from "../common/IconButton.svelte"
export let component
export let components
let errors = []
const props_to_ignore = ["_component", "_children", "_styles", "_code", "_id"]
$: componentDef = components[component._component]
</script>
<div class="root">
<form on:submit|preventDefault class="uk-form-stacked form-root">
{#if componentDef}
{#each Object.entries(componentDef.props) as [prop_name, prop_def], index}
{#if prop_def !== 'event'}
<div class="prop-container">
<PropControl
{prop_name}
prop_value={component[prop_name]}
prop_definition={prop_def}
{index}
disabled={false} />
</div>
{/if}
{/each}
{/if}
</form>
</div>
<style>
.root {
font-size: 10pt;
width: 100%;
}
.form-root {
display: flex;
flex-wrap: wrap;
}
.prop-container {
flex: 1 1 auto;
min-width: 250px;
}
</style>

View File

@ -1,7 +1,6 @@
<script>
import PropertyControl from "./PropertyControl.svelte"
import InputGroup from "../common/Inputs/InputGroup.svelte"
import Input from "../common/Input.svelte"
import Input from "./PropertyPanelControls/Input.svelte"
import { goto } from "@sveltech/routify"
import { excludeProps } from "./propertyCategories.js"

View File

@ -1,63 +0,0 @@
<script>
import { backendUiStore } from "builderStore"
import IconButton from "../common/IconButton.svelte"
import Input from "../common/Input.svelte"
export let value = ""
export let onChanged = () => {}
export let type = ""
export let options = []
export let styleBindingProperty = ""
$: bindOptionToStyle = !!styleBindingProperty
</script>
<div class="unbound-container">
{#if type === 'bool'}
<div>
<IconButton
icon={value == true ? 'check-square' : 'square'}
size="19"
on:click={() => onChanged(!value)} />
</div>
{:else if type === 'models'}
<select
class="uk-select uk-form-small"
bind:value
on:change={() => {
onChanged(value)
}}>
{#each $backendUiStore.models || [] as option}
<option value={option}>{option.name}</option>
{/each}
</select>
{:else if type === 'options' || type === 'models'}
<select
class="uk-select uk-form-small"
{value}
on:change={ev => onChanged(ev.target.value)}>
{#each options || [] as option}
{#if bindOptionToStyle}
<option style={`${styleBindingProperty}: ${option};`} value={option}>
{option}
</option>
{:else}
<option value={option}>{option}</option>
{/if}
{/each}
</select>
{/if}
</div>
<style>
.unbound-container {
display: flex;
}
.bound-header > div:nth-child(1) {
flex: 1 0 auto;
width: 30px;
color: var(--secondary50);
padding-left: 5px;
}
</style>

View File

@ -8,7 +8,6 @@
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"

View File

@ -48,7 +48,7 @@ export const createProps = (componentDefinition, derivedFromProps) => {
assign(props, derivedFromProps)
}
if (componentDefinition.children !== false && isUndefined(props._children)) {
if (isUndefined(props._children)) {
props._children = []
}

View File

@ -1,4 +1,4 @@
import Input from "../common/Input.svelte"
import Input from "./PropertyPanelControls/Input.svelte"
import OptionSelect from "./OptionSelect.svelte"
import FlatButtonGroup from "./FlatButtonGroup.svelte"
import Colorpicker from "@budibase/colorpicker"
@ -70,6 +70,20 @@ export const layout = [
{ label: "no wrap", value: "noWrap" },
],
},
{
label: "Gap",
key: "gap",
control: OptionSelect,
options: [
{ label: "None", value: "0px" },
{ label: "4px", value: "4px" },
{ label: "8px", value: "8px" },
{ label: "16px", value: "16px" },
{ label: "20px", value: "20px" },
{ label: "32px", value: "32px" },
{ label: "64px", value: "64px" },
],
},
]
export const margin = [

View File

@ -1,4 +1,4 @@
import Input from "../common/Input.svelte"
import Input from "./PropertyPanelControls/Input.svelte"
import OptionSelect from "./OptionSelect.svelte"
import Checkbox from "../common/Checkbox.svelte"
import ModelSelect from "components/userInterface/ModelSelect.svelte"
@ -306,47 +306,177 @@ export default {
label: "destinationUrl",
key: "destinationUrl",
control: Input,
placeholder: "/table/{{context._id}}",
placeholder: "/table/_id",
},
],
},
},
{
_component: "@budibase/materialdesign-components/BasicCard",
name: "Card",
description:
"A basic card component that can contain content and actions.",
description: "Card components",
icon: "ri-layout-bottom-line",
children: [],
properties: {
design: { ...all },
settings: [
{
label: "Heading",
key: "heading",
control: Input,
placeholder: "text",
commonProps: {},
children: [
{
_component: "@budibase/standard-components/card",
name: "Vertical",
description:
"A basic card component that can contain content and actions.",
icon: "ri-layout-column-line",
children: [],
properties: {
design: { ...all },
settings: [
{
label: "Image",
key: "imageUrl",
control: Input,
placeholder: "Image",
},
{
label: "Heading",
key: "heading",
control: Input,
placeholder: "Heading",
},
{
label: "Description",
key: "description",
control: Input,
placeholder: "Description",
},
{
label: "Link Text",
key: "linkText",
control: Input,
placeholder: "Link Text",
},
{
label: "Link Url",
key: "linkUrl",
control: Input,
placeholder: "Link URL",
},
{
label: "Link Color",
key: "color",
control: Input,
placeholder: "Link Color",
},
{
label: "Hover Color",
key: "linkHoverColor",
control: Input,
placeholder: "Hover Color",
},
{
label: "Image Height",
key: "imageHeight",
control: OptionSelect,
options: ["12rem", "16rem", "20rem", "24rem"],
placeholder: "Image Height",
},
{
label: "Card Width",
key: "cardWidth",
control: OptionSelect,
options: ["16rem", "20rem", "24rem"],
placeholder: "Card Width",
},
],
},
{
label: "Subheading",
key: "subheading",
control: Input,
placeholder: "text",
},
{
_component: "@budibase/standard-components/cardhorizontal",
name: "Horizontal",
description:
"A basic card component that can contain content and actions.",
icon: "ri-layout-row-line",
children: [],
properties: {
design: { ...all },
settings: [
{
label: "Image",
key: "imageUrl",
control: Input,
placeholder: "Image",
},
{
label: "Heading",
key: "heading",
control: Input,
placeholder: "Heading",
},
{
label: "Description",
key: "description",
control: Input,
placeholder: "Description",
},
{
label: "Subtext",
key: "subtext",
control: Input,
placeholder: "Subtext",
},
{
label: "Link Text",
key: "linkText",
control: Input,
placeholder: "Link Text",
},
{
label: "Link Url",
key: "linkUrl",
control: Input,
placeholder: "Link URL",
},
{
label: "Link Color",
key: "color",
control: Input,
placeholder: "Link Color",
},
{
label: "Hover Color",
key: "linkHoverColor",
control: Input,
placeholder: "Hover Color",
},
{
label: "Card Width",
key: "cardWidth",
control: OptionSelect,
options: [
"24rem",
"28rem",
"32rem",
"40rem",
"48rem",
"60rem",
"100%",
],
placeholder: "Card Height",
},
{
label: "Image Width",
key: "imageWidth",
control: OptionSelect,
options: ["8rem", "12rem", "16rem"],
placeholder: "Image Width",
},
{
label: "Image Height",
key: "imageHeight",
control: OptionSelect,
options: ["8rem", "12rem", "16rem", "auto"],
placeholder: "Image Height",
},
],
},
{
label: "Content",
key: "content",
control: Input,
placeholder: "text",
},
{
label: "Image",
key: "imageUrl",
control: Input,
placeholder: "src",
},
],
},
},
],
},
{
name: "Table",
@ -396,11 +526,6 @@ export default {
},
],
},
template: {
component: "@budibase/materialdesign-components/Form",
description: "Form for saving a record",
name: "@budibase/materialdesign-components/recordForm",
},
},
{
_component: "@budibase/standard-components/dataformwide",
@ -430,129 +555,461 @@ export default {
],
},
{
name: "Donut Chart",
_component: "@budibase/standard-components/donut",
description: "Donut chart",
icon: "ri-pie-chart-fill",
properties: {
settings: [
{
label: "Table",
key: "model",
control: ModelSelect,
},
{
label: "Animate Chart",
key: "isAnimated",
valueKey: "checked",
control: Checkbox,
},
{
label: "Hover Highlight",
key: "hasHoverAnimation",
valueKey: "checked",
control: Checkbox,
},
{
label: "Keep Last Hover",
key: "hasLastHoverSliceHighlighted",
valueKey: "checked",
control: Checkbox,
},
{
label: "Colors",
key: "color",
control: OptionSelect,
options: [
"britecharts",
"blueGreen",
"green",
"grey",
"orange",
"pink",
"purple",
"red",
"teal",
"yellow",
],
},
{
label: "Name Field",
key: "nameKey",
control: Input,
},
{
label: "Value Field",
key: "valueKey",
control: Input,
},
{
label: "External Radius",
key: "externalRadius",
control: Input,
},
{
label: "Internal Radius",
key: "internalRadius",
control: Input,
},
{
label: "Radius Offset",
key: "radiusHoverOffset ",
control: Input,
},
{
label: "Show Legend",
key: "useLegend ",
valueKey: "checked",
control: Checkbox,
},
{
label: "Horizontal Legend",
key: "horizontalLegend",
valueKey: "checked",
control: Checkbox,
},
{
label: "Legend Width",
key: "legendWidth",
control: Input,
},
{
label: "Legend Height",
key: "legendHeight",
control: Input,
},
],
},
children: [],
},
{
name: "Data List",
_component: "@budibase/standard-components/datalist",
description: "Shiny list",
icon: "ri-file-list-fill",
properties: {
design: { ...all },
settings: [{ label: "Table", key: "model", control: ModelSelect }],
},
children: [],
},
{
name: "List",
_component: "@budibase/standard-components/list",
description: "Renders all children once per record, of a given table",
icon: "ri-file-list-fill",
properties: {
design: { ...all },
settings: [{ label: "Table", key: "model", control: ModelSelect }],
},
name: "Chart",
description: "Shiny chart",
icon: "ri-bar-chart-fill",
children: [
{
_component: "@budibase/standard-components/heading",
name: "Headline",
description: "A component for displaying heading text",
icon: "ri-heading",
name: "Donut",
_component: "@budibase/standard-components/donut",
description: "Donut chart",
icon: "ri-pie-chart-fill",
properties: {
settings: [
{
label: "Table",
key: "model",
control: ModelSelect,
},
{
label: "Animate Chart",
key: "isAnimated",
valueKey: "checked",
control: Checkbox,
},
{
label: "Hover Highlight",
key: "hasHoverAnimation",
valueKey: "checked",
control: Checkbox,
},
{
label: "Keep Last Hover",
key: "hasLastHoverSliceHighlighted",
valueKey: "checked",
control: Checkbox,
},
{
label: "Colors",
key: "color",
control: OptionSelect,
options: [
"britecharts",
"blueGreen",
"green",
"grey",
"orange",
"pink",
"purple",
"red",
"teal",
"yellow",
],
},
{
label: "Name Field",
key: "nameKey",
control: Input,
},
{
label: "Value Field",
key: "valueKey",
control: Input,
},
{
label: "External Radius",
key: "externalRadius",
control: Input,
},
{
label: "Internal Radius",
key: "internalRadius",
control: Input,
},
{
label: "Radius Offset",
key: "radiusHoverOffset ",
control: Input,
},
{
label: "Show Legend",
key: "useLegend ",
valueKey: "checked",
control: Checkbox,
},
{
label: "Horizontal Legend",
key: "horizontalLegend",
valueKey: "checked",
control: Checkbox,
},
{
label: "Legend Width",
key: "legendWidth",
control: Input,
},
],
},
},
{
name: "Bar",
_component: "@budibase/standard-components/bar",
description: "Bar chart",
icon: "ri-bar-chart-fill",
properties: {
settings: [
{
label: "Table",
key: "model",
control: ModelSelect,
},
{
label: "Name Label",
key: "nameLabel",
control: Input,
},
{
label: "Value Label",
key: "valueLabel",
control: Input,
},
{
label: "Y Axis Label",
key: "yAxisLabel",
control: Input,
},
{
label: "X Axis Label",
key: "xAxisLabel",
control: Input,
},
{
label: "X Axis Label Offset",
key: "xAxisLabelOffset",
control: Input,
},
{
label: "Y Axis Label Offset",
key: "yAxisLabelOffset",
control: Input,
},
{
label: "Enable Labels",
key: "enableLabels",
control: Checkbox,
valueKey: "checked",
},
{
label: "Colors",
key: "color",
control: OptionSelect,
options: [
{ label: "Normal", value: "britecharts" },
{ label: "Blue Green", value: "blueGreen" },
{ label: "Green", value: "green" },
{ label: "Grey", value: "grey" },
{ label: "Orange", value: "orange" },
{ label: "Pink", value: "pink" },
{ label: "Purple", value: "purple" },
{ label: "Red", value: "red" },
{ label: "Teal", value: "teal" },
{ label: "Yellow", value: "yellow" },
],
},
{
label: "Gradients",
key: "gradient",
control: OptionSelect,
options: [
{ value: "", label: "None" },
{ value: "bluePurple", label: "Blue Purple" },
{ value: "greenBlue", label: "Green Blue" },
{ value: "orangePink", label: "Orange Pink" },
],
},
{
label: "Highlight Single Bar",
key: "hasSingleBarHighlight",
control: Checkbox,
valueKey: "checked",
},
{
label: "Width",
key: "width",
control: Input,
},
{
label: "Height",
key: "height",
control: Input,
},
{
label: "Animate",
key: "isAnimate",
control: Checkbox,
valueKey: "checked",
},
{
label: "Horizontal",
key: "isHorizontal",
control: Checkbox,
valueKey: "checked",
},
{
label: "Label Number Format",
key: "labelsNumberFormat",
control: Input,
},
],
},
},
{
name: "Groupedbar",
_component: "@budibase/standard-components/groupedbar",
description: "Groupedbar chart",
icon: "ri-bar-chart-grouped-fill",
properties: {
settings: [
{
label: "Table",
key: "model",
control: ModelSelect,
},
{
label: "Color",
key: "color",
control: OptionSelect,
options: [
"britecharts",
"blueGreen",
"green",
"grey",
"orange",
"pink",
"purple",
"red",
"teal",
"yellow",
],
},
{
label: "Height",
key: "height",
control: Input,
},
{
label: "Width",
key: "width",
control: Input,
},
{
label: "Aspect Ratio",
key: "aspectRatio",
control: Input,
},
{
label: "Grid",
key: "grid",
control: OptionSelect,
options: ["vertical", "horizontal", "full"],
},
{
label: "Group Label",
key: "groupLabel",
control: Input,
},
{
label: "Name Label",
key: "nameLabel",
control: Input,
},
{
label: "Value Label",
key: "valueLabel",
control: Input,
},
{
label: "Y Ticks",
key: "yTicks",
control: Input,
},
{
label: "Y Tick Text Offset",
key: "yTickTextOffset",
control: Input,
},
{
label: "Is Animated",
key: "isAnimated",
valueKey: "checked",
control: Checkbox,
},
{
label: "Is Horizontal",
key: "isHorizontal",
valueKey: "checked",
control: Checkbox,
},
{
label: "Tooltip Title",
key: "tooltipTitle",
control: Input,
},
],
},
},
{
name: "Line",
_component: "@budibase/standard-components/line",
description: "Line chart",
icon: "ri-line-chart-fill",
properties: {
settings: [
{
label: "Table",
key: "model",
control: ModelSelect,
},
{
label: "Colors",
key: "color",
control: OptionSelect,
options: [
"britecharts",
"blueGreen",
"green",
"grey",
"orange",
"pink",
"purple",
"red",
"teal",
"yellow",
],
},
{
label: "Gradients",
key: "lineGradient",
control: OptionSelect,
options: [
{ value: "", label: "None" },
{ value: "bluePurple", label: "Blue Purple" },
{ value: "greenBlue", label: "Green Blue" },
{ value: "orangePink", label: "Orange Pink" },
],
},
{
label: "Line Curve",
key: "lineCurve",
control: OptionSelect,
options: [
"linear",
"basis",
"natural",
"monotoneX",
"monotoneY",
"step",
"stepAfter",
"stepBefore",
"cardinal",
"catmullRom",
],
},
{
label: "X Axis Value Type",
key: "xAxisValueType",
control: OptionSelect,
options: ["date", "number"],
},
{
label: "Grid",
key: "grid",
control: OptionSelect,
options: ["vertical", "horizontal", "full"],
},
{
label: "Date Label",
key: "dateLabel",
control: Input,
},
{
label: "Topic Label",
key: "topicLabel",
control: Input,
},
{
label: "Value Label",
key: "valueLabel",
control: Input,
},
{
label: "X Axis Label",
key: "xAxisLabel",
control: Input,
},
{
label: "Y Axis Label",
key: "yAxisLabel",
control: Input,
},
{
label: "Show All Datapoints",
key: "shouldShowAllDataPoints",
valueKey: "checked",
control: Checkbox,
},
{
label: "Width",
key: "width",
control: Input,
},
{
label: "Height",
key: "height",
control: Input,
},
{
label: "Is Animated",
key: "isAnimated",
control: Checkbox,
valueKey: "checked",
},
{
label: "Locale",
key: "locale",
control: OptionSelect,
options: ["en-GB", "en-US"],
},
{
label: "X Axis Value Type",
key: "xAxisValueType",
control: OptionSelect,
options: ["date", "numeric"],
},
{
label: "X Axis Format",
key: "xAxisFormat",
control: OptionSelect,
options: [
"day-month",
"minute-hour",
"hour-daymonth",
"month-year",
"custom",
],
},
{
label: "X Axis Custom Format",
key: "xAxisCustomFormat",
control: Input,
},
{
label: "Tooltip Title",
key: "tooltipTitle",
control: Input,
},
],
},
},
],
},

View File

@ -1,5 +1,6 @@
<script>
import { backendUiStore } from "builderStore"
import { Input } from "@budibase/bbui"
export let value
</script>
@ -19,8 +20,7 @@
<label class="uk-form-label fields">Fields</label>
{#each Object.keys(value.model.schema) as field}
<div class="uk-form-controls uk-margin">
<label class="uk-form-label">{field}</label>
<input type="text" class="budibase__input" bind:value={value[field]} />
<Input bind:value={value[field]} label={field} />
</div>
{/each}
</div>

View File

@ -5,7 +5,7 @@
import { notifier } from "builderStore/store/notifications"
import WorkflowBlockSetup from "./WorkflowBlockSetup.svelte"
import DeleteWorkflowModal from "./DeleteWorkflowModal.svelte"
import { Button } from "@budibase/bbui"
import { Button, Input } from "@budibase/bbui"
const { open, close } = getContext("simple-modal")
@ -112,13 +112,7 @@
<div class="panel-body">
<div class="block-label">Workflow: {workflow.name}</div>
<div class="config-item">
<label>Name</label>
<div class="form">
<input
type="text"
class="budibase_input"
bind:value={workflow.name} />
</div>
<Input label="Name" bind:value={workflow.name} thin />
</div>
<div class="config-item">
<label class="uk-form-label">User Access</label>
@ -194,28 +188,12 @@
margin-bottom: 20px;
}
.budibase_input {
height: 36px;
width: 244px;
border-radius: 3px;
background-color: var(--grey-2);
border: 1px solid var(--grey-2);
text-align: left;
color: var(--ink);
font-size: 14px;
padding-left: 12px;
}
header > span {
color: var(--grey-5);
margin-right: 20px;
cursor: pointer;
}
.form {
margin-top: 12px;
}
label {
font-weight: 500;
font-size: 14px;
@ -226,7 +204,7 @@
position: absolute;
bottom: 20px;
display: grid;
width: 100%;
width: 260px;
gap: 12px;
}

View File

@ -3,6 +3,7 @@
import ComponentSelector from "./ParamInputs/ComponentSelector.svelte"
import ModelSelector from "./ParamInputs/ModelSelector.svelte"
import RecordSelector from "./ParamInputs/RecordSelector.svelte"
import { Input, TextArea, Select } from "@budibase/bbui"
export let workflowBlock
@ -18,42 +19,34 @@
<div class="block-field">
<label class="label">{parameter}</label>
{#if Array.isArray(type)}
<select class="budibase_input" bind:value={workflowBlock.args[parameter]}>
<Select bind:value={workflowBlock.args[parameter]} thin>
{#each type as option}
<option value={option}>{option}</option>
{/each}
</select>
</Select>
{:else if type === 'component'}
<ComponentSelector bind:value={workflowBlock.args[parameter]} />
{:else if type === 'accessLevel'}
<select class="budibase_input" bind:value={workflowBlock.args[parameter]}>
<Select bind:value={workflowBlock.args[parameter]} thin>
<option value="ADMIN">Admin</option>
<option value="POWER_USER">Power User</option>
</select>
</Select>
{:else if type === 'password'}
<input
type="password"
class="budibase_input"
bind:value={workflowBlock.args[parameter]} />
<Input type="password" thin bind:value={workflowBlock.args[parameter]} />
{:else if type === 'number'}
<input
type="number"
class="budibase_input"
bind:value={workflowBlock.args[parameter]} />
<Input type="number" thin bind:value={workflowBlock.args[parameter]} />
{:else if type === 'longText'}
<textarea
<TextArea
type="text"
class="budibase_input"
bind:value={workflowBlock.args[parameter]} />
thin
bind:value={workflowBlock.args[parameter]}
label="" />
{:else if type === 'model'}
<ModelSelector bind:value={workflowBlock.args[parameter]} />
{:else if type === 'record'}
<RecordSelector bind:value={workflowBlock.args[parameter]} />
{:else if type === 'string'}
<input
type="text"
class="budibase_input"
bind:value={workflowBlock.args[parameter]} />
<Input type="text" thin bind:value={workflowBlock.args[parameter]} />
{/if}
</div>
{/each}
@ -62,17 +55,6 @@
.block-field {
display: grid;
}
.budibase_input {
height: 36px;
border-radius: 5px;
background-color: var(--grey-2);
border: 1px solid var(--grey-2);
text-align: left;
color: var(--ink);
font-size: 14px;
padding-left: 12px;
margin-top: 8px;
}
label {
text-transform: capitalize;

View File

@ -2,6 +2,7 @@
import { store, backendUiStore, workflowStore } from "builderStore"
import { notifier } from "builderStore/store/notifications"
import ActionButton from "components/common/ActionButton.svelte"
import { Input } from "@budibase/bbui"
export let onClosed
@ -26,8 +27,7 @@
Create Workflow
</header>
<div>
<label class="uk-form-label" for="form-stacked-text">Name</label>
<input class="uk-input" type="text" bind:value={name} />
<Input bind:value={name} label="Name" />
</div>
<footer>
<a href="https://docs.budibase.com">

View File

@ -11,17 +11,18 @@
<link rel='icon' type='image/png' href='/_builder/favicon.png'>
<link rel='stylesheet' href='/_builder/global.css'>
<link rel='stylesheet' href='/_builder/codemirror.css'>
<link rel='stylesheet' href='/_builder/budibase.css'>
<link rel='stylesheet' href='/_builder/monokai.css'>
<link rel='stylesheet' href='/_builder/bundle.css'>
<link rel='stylesheet' href='/_builder/bbui.css'>
<link rel='stylesheet' href='/_builder/fonts.css'>
<link rel='stylesheet' href="/_builder/uikit.min.css">
</head>
<body id="app">
<script src='/_builder/bundle.js'></script>
</body>
</html>

View File

@ -5,7 +5,6 @@
import ComponentsHierarchyChildren from "components/userInterface/ComponentsHierarchyChildren.svelte"
import IconButton from "components/common/IconButton.svelte"
import CurrentItemPreview from "components/userInterface/AppPreview"
import PageView from "components/userInterface/PageView.svelte"
import ComponentPropertiesPanel from "components/userInterface/ComponentPropertiesPanel.svelte"
import ComponentSelectionList from "components/userInterface/ComponentSelectionList.svelte"
import Switcher from "components/common/Switcher.svelte"

View File

@ -2,17 +2,11 @@
import Modal from "svelte-simple-modal"
import { Home as Link } from "@budibase/bbui"
import {
SettingsIcon,
AppsIcon,
UpdatesIcon,
HostingIcon,
DocumentationIcon,
TutorialsIcon,
CommunityIcon,
ContributionIcon,
BugIcon,
EmailIcon,
TwitterIcon,
} from "components/common/Icons/"
</script>
@ -24,16 +18,11 @@
</div>
<div class="nav-section">
<div class="nav-section-title">Build</div>
<Link icon={AppsIcon} title="Apps" href="/" active />
<Link
icon={HostingIcon}
title="Hosting"
href="https://portal.budi.live/" />
</div>
<div class="nav-section">
<div class="nav-section-title">Learn</div>
<Link
icon={DocumentationIcon}
title="Documentation"
@ -42,23 +31,11 @@
icon={CommunityIcon}
title="Community"
href="https://forum.budibase.com/" />
</div>
<div class="nav-section">
<div class="nav-section-title">Contact</div>
<Link
icon={ContributionIcon}
title="Contribute"
href="https://github.com/Budibase/budibase" />
<Link
icon={BugIcon}
title="Report bug"
href="https://github.com/Budibase/budibase/issues" />
<Link icon={EmailIcon} title="Email" href="mailto:hi@budibase.com" />
<Link
icon={TwitterIcon}
title="Twitter"
href="https://twitter.com/budibase" />
title="Raise an issue"
href="https://github.com/Budibase/budibase" />
</div>
</div>
@ -88,6 +65,7 @@
padding: 20px;
display: flex;
flex-direction: column;
border-right: var(--border-light);
}
.home-logo {
@ -105,11 +83,4 @@
display: flex;
flex-direction: column;
}
.nav-section-title {
font-size: 20px;
color: var(--ink);
font-weight: 600;
margin-bottom: 12px;
}
</style>

View File

@ -66,7 +66,9 @@
<div class="header">
<div class="welcome">Welcome to the Budibase Beta</div>
<Button purple large on:click={showCreateAppModal}>Create New Web App</Button>
<Button primary purple on:click={showCreateAppModal}>
Create New Web App
</Button>
</div>
<div class="banner">
@ -95,9 +97,9 @@
}
.welcome {
font-size: 42px;
font-size: var(--font-size-3xl);
color: var(--ink);
font-weight: 700;
font-weight: 600;
}
.banner {
@ -108,7 +110,7 @@
text-align: center;
color: white;
margin: 20px 80px 40px 80px;
border-radius: 10px;
border-radius: 16px;
}
.banner img {

View File

@ -72,16 +72,6 @@ describe("createDefaultProps", () => {
expect(props._children).toEqual([])
})
it("should create a object without _children array, when children===false ", () => {
const comp = getcomponent()
comp.children = false
const { props, errors } = createProps(comp)
expect(errors).toEqual([])
expect(props._children).not.toBeDefined()
})
it("should create a object with single empty array, when prop definition is 'event' ", () => {
const comp = getcomponent()
comp.props.onClick = "event"
@ -104,24 +94,22 @@ describe("createDefaultProps", () => {
expect(props._children).toEqual([])
})
it("should create a _children array when children not defined ", () => {
const comp = getcomponent()
const { props, errors } = createProps(comp)
expect(errors).toEqual([])
expect(props._children).toBeDefined()
expect(props._children).toEqual([])
})
it("should not create _children array when children=false ", () => {
it("should always create _children ", () => {
const comp = getcomponent()
comp.children = false
const { props, errors } = createProps(comp)
const createRes1 = createProps(comp)
expect(errors).toEqual([])
expect(props._children).not.toBeDefined()
expect(createRes1.errors).toEqual([])
expect(createRes1.props._children).toBeDefined()
const comp2 = getcomponent()
comp2.children = true
const createRes2 = createProps(comp)
expect(createRes2.errors).toEqual([])
expect(createRes2.props._children).toBeDefined()
})
it("should create an object with multiple prop names", () => {

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,6 @@
"author": "",
"license": "ISC",
"dependencies": {
"@budibase/standard-components": "0.x",
"@budibase/materialdesign-components": "0.x"
"@budibase/standard-components": "0.x"
}
}

View File

@ -2,7 +2,7 @@
"title": "Test App",
"favicon": "./_shared/favicon.png",
"stylesheets": [],
"componentLibraries": ["@budibase/standard-components", "@budibase/materialdesign-components"],
"componentLibraries": ["@budibase/standard-components"],
"props" : {
"_component": "@budibase/standard-components/container",
"_children": [],

View File

@ -2,7 +2,7 @@
"title": "Test App",
"favicon": "./_shared/favicon.png",
"stylesheets": [],
"componentLibraries": ["@budibase/standard-components", "@budibase/materialdesign-components"],
"componentLibraries": ["@budibase/standard-components"],
"props" : {
"_component": "@budibase/standard-components/container",
"_children": [],

View File

@ -2,7 +2,7 @@
"title": "Test App",
"favicon": "./_shared/favicon.png",
"stylesheets": [],
"componentLibraries": ["@budibase/standard-components", "@budibase/materialdesign-components"],
"componentLibraries": ["@budibase/standard-components" ],
"props" : {
"_component": "@budibase/standard-components/container",
"_children": [],

View File

@ -1,6 +0,0 @@
.DS_Store
node_modules
yarn.lock
package-lock.json
dist/*
public/build

View File

@ -1,3 +0,0 @@
*
!dist/*
!components.json

View File

@ -1,33 +0,0 @@
*Psst — looking for an app template? Go here --> [sveltejs/template](https://github.com/sveltejs/template)*
---
# component-template
A base for building shareable Svelte components. Clone it with [degit](https://github.com/Rich-Harris/degit):
```bash
npx degit sveltejs/component-template my-new-component
cd my-new-component
npm install # or yarn
```
Your component's source code lives in `src/index.html`.
TODO
* [ ] some firm opinions about the best way to test components
* [ ] update `degit` so that it automates some of the setup work
## Setting up
* Run `npm init` (or `yarn init`)
* Replace this README with your own
## Consuming components
Your package.json has a `"svelte"` field pointing to `src/index.html`, which allows Svelte apps to import the source code directly, if they are using a bundler plugin like [rollup-plugin-svelte](https://github.com/rollup/rollup-plugin-svelte) or [svelte-loader](https://github.com/sveltejs/svelte-loader) (where [`resolve.mainFields`](https://webpack.js.org/configuration/resolve/#resolve-mainfields) in your webpack config includes `"svelte"`). **This is recommended.**
For everyone else, `npm run build` will bundle your component's source code into a plain JavaScript module (`index.mjs`) and a UMD script (`index.js`). This will happen automatically when you publish your component to npm, courtesy of the `prepublishOnly` hook in package.json.

View File

@ -1,458 +0,0 @@
{
"_lib": "./dist/index.js",
"_templates": {
"indexDatatable": {
"description": "Datatable based on an Index",
"component": "Datatable"
},
"recordForm": {
"description": "Form for saving a record",
"component": "Form"
}
},
"Body1": {
"name": "Body1",
"description": "Sets the font properties as Roboto Body 1",
"props": {
"text": "string"
},
"tags": []
},
"Body2": {
"name": "Body2",
"description": "Sets the font properties as Roboto Body 2",
"props": {
"text": "string"
},
"tags": []
},
"Select": {
"name": "Select",
"description": "A material design select (aka Dropdown, aka Combobox)",
"props": {
"onSelect": "event",
"value": "string",
"width": "string",
"variant": {
"type": "options",
"options": [
"filled", "outlined"
]
},
"disabled": "bool",
"required": "bool",
"label": "string",
"helperText": "string",
"persistent": "bool"
}
},
"List": {
"name": "List",
"description": "A Material Design List Component.",
"props": {
"onSelect": "event",
"singleSelection": "bool",
"variant": {
"type": "options",
"options": ["one-line", "two-line"],
"default": "one-line"
},
"inputElement": {
"type": "options",
"options": ["None", "Radiobutton", "Checkbox"],
"default": "None"
}
}
},
"ListItem": {
"name": "ListItem",
"description": "Use as item in a 'List' or 'Select' component",
"props": {
"value": "string",
"text": "string",
"secondaryText": "string",
"leadingIcon": "string",
"trailingIcon": "string",
"disabled": "bool",
"dividerAfter": "bool"
}
},
"Button": {
"name": "Button",
"children": false,
"description": "A Material Design button with different variations. It renders as an anchor if href is passed to it.",
"props": {
"onClick": "event",
"variant": {
"type": "options",
"options": ["text", "raised", "unelevated", "outlined"],
"default": "text"
},
"colour": {
"type": "options",
"options": ["primary", "secondary"],
"default": "primary"
},
"size": {
"type": "options",
"options": ["small", "medium", "large"],
"default": "medium"
},
"href": "string",
"icon": "string",
"trailingIcon": "bool",
"fullwidth": "bool",
"text": "string",
"disabled": "bool"
},
"tags": []
},
"Caption": {
"name": "Caption",
"description": "Sets the font properties as Roboto Caption",
"props": {
"text": "string"
},
"tags": []
},
"BasicCard": {
"name": "BasicCard",
"description": "This is a basic card",
"props": {
"heading": "string",
"subheading": "string",
"content": "string",
"imageUrl": "string",
"button1Text": "string",
"button2Text": "string",
"cardClick": "event",
"button1Click": "event",
"button2Click": "event"
}
},
"Card": {
"name": "Card",
"description": "A Material Card container. Accepts CardHeader, CardBody and CardFooter as possible children",
"props": {
"width": {"type": "string", "default": "350px"},
"height": "string",
"variant": {
"type": "options",
"options": ["standard", "outlined"],
"default": "outlined"
}
}
},
"CardBody": {
"name": "CardBody",
"description": "A Material CardBody component. Contains the main content of a Material Card component",
"props": {
"onClick": "event"
}
},
"CardImage": {
"name": "CardImage",
"description": "An image component for the Material Card component",
"props": {
"displayHorizontal": "bool",
"url": "string",
"title": "string",
"subtitle": "string"
}
},
"CardHeader": {
"name": "CardHeader",
"description": "Displays a icon, title and subtitle above main body of the Material Card component",
"props": {
"title": "string",
"subtitle": "string",
"icon": "string"
}
},
"CardFooter": {
"name": "CardFooter",
"description": "Displays buttons / icon buttons as actions for the Material Card component",
"props": {
"padding": "string"
}
},
"Checkbox": {
"name": "Checkbox",
"description": "A Material Design checkbox. Supports aligning label before or after checkbox.",
"props": {
"onClick": "event",
"id": "string",
"label": "string",
"disabled": "bool",
"alignEnd": "bool",
"indeterminate": "bool",
"checked": "bool"
},
"tags": []
},
"Checkboxgroup": {
"name": "Checkboxgroup",
"description": "A group of material design checkboxes. Supports row and column orientation.",
"props": {
"onChange": "event",
"label":"string",
"orientation": {
"type": "options",
"options": ["row", "column"],
"default": "row"
},
"disabled": "bool",
"alignEnd": "bool"
}
},
"Datatable": {
"name": "Datatable",
"description": "A Material Design component to represent tabular data.",
"props": {
"onLoad":"event"
},
"tags": []
},
"DatatableHead": {
"name": "DatatableHead",
"description": "Material Design <thead>.",
"props": {}
},
"DatatableCell": {
"name": "DatatableCell",
"description": "Material Design <td>.",
"props": {}
},
"DatatableBody": {
"name": "DatatableBody",
"description": "Material Design <tbody>.",
"props": {}
},
"DatatableRow": {
"name": "DatatableRow",
"description": "Material Design <tr>.",
"props": {}
},
"DatePicker": {
"name": "DatePicker",
"description": "Material Design DatePicker",
"props": {
"date": "string",
"label": "string",
"onSelect": "event"
},
"tags": []
},
"H1": {
"name": "H1",
"description": "Sets the font properties as Roboto Headline1",
"props": {
"text": "string"
},
"tags": []
},
"H2": {
"name": "H2",
"description": "Sets the font properties as Roboto Headline2",
"props": {
"text": "string"
},
"tags": []
},
"H3": {
"name": "H3",
"description": "Sets the font properties as Roboto Headline3",
"props": {
"text": "string"
},
"tags": []
},
"H4": {
"name": "H4",
"description": "Sets the font properties as Roboto Headline4",
"props": {
"text": "string"
},
"tags": []
},
"H5": {
"name": "H5",
"description": "Sets the font properties as Roboto Headline5",
"props": {
"text": "string"
},
"tags": []
},
"H6": {
"name": "H6",
"description": "Sets the font properties as Roboto Headline6",
"props": {
"text": "string"
},
"tags": []
},
"IconButton": {
"name": "IconButton",
"description": "An icon button component",
"props": {
"onClick": "event",
"disabled": "bool",
"href": "string",
"icon": "string",
"size": {
"type":"options",
"options": ["small", "medium", "large"],
"default": "medium"
}
},
"tags": []
},
"Label": {
"name": "Label",
"description": "A simple label component that displays its text in the standard Roboto Material Design font",
"props": {
"text": "string",
"bold": "bool"
},
"tags": []
},
"Menu": {
"name": "Menu",
"description": "A Material Design menu component. Anchor to other components to create a pop-out menu.",
"props": {
"onSelect": "event",
"width": "string",
"open": "bool",
"useFixedPosition": "bool",
"absolutePositionX": "number",
"absolutePositionY": "number"
}
},
"Overline": {
"name": "Overline",
"description": "Sets the font properties as Roboto Overline",
"props": {
"text": "string"
},
"tags": []
},
"Radiobutton": {
"name": "Radiobutton",
"description": "A Material Design radiobutton. Supports aligning label before or after radiobutton.",
"props": {
"onClick": "event",
"id": "string",
"label": "string",
"name": "string",
"disabled": "bool",
"alignEnd": "bool"
},
"tags": []
},
"Radiobuttongroup": {
"name": "Radiobuttongroup",
"description": "A Material Design Radiobutton group. Supports row and column orientation.",
"props": {
"onChange": "event",
"label": "string",
"name": "string",
"orientation": {
"type": "options",
"options": ["row", "column"],
"default": "row"
},
"fullwidth": "bool",
"alignEnd": "bool",
"disabled": "bool"
}
},
"Sub1": {
"name": "Sub1",
"description": "Sets the font properties as Roboto Subtitle1",
"props": {
"text": "string"
},
"tags": []
},
"Sub2": {
"name": "Sub2",
"description": "Sets the font properties as Roboto Subtitle2",
"props": {
"text": "string"
},
"tags": []
},
"Textfield": {
"name": "Textfield",
"description": "A Material Design textfield with multiple variants. Can also be converted to a text area / multine text field.",
"props": {
"onChange": "event",
"value": "string",
"label": "string",
"variant": {
"type": "options",
"options": ["standard", "outlined", "filled"],
"default": "standard"
},
"disabled": "bool",
"fullwidth": "bool",
"colour": {
"type": "options",
"options": ["primary", "secondary"],
"default": "primary"
},
"size":{
"type": "options",
"options": ["small", "medium", "large"],
"default": "medium"
},
"type": {
"type": "options",
"options": ["text", "password"],
"default": "text"
},
"required": "bool",
"minLength": "number",
"maxLength": "number",
"helperText": "string",
"placeholder": "string",
"icon": "string",
"trailingIcon": "bool",
"textarea": "bool",
"persistent": "bool"
},
"tags": []
},
"Switch": {
"name": "Switch",
"description": "A material design switch component",
"props": {
"alignEnd": "bool",
"disabled": "bool",
"checked": "bool",
"label": "string",
"id": "string"
},
"tags": []
},
"Slider": {
"name": "Slider",
"description": "A material design slider component",
"props": {
"variant": {
"type": "options",
"options": ["continuous", "discrete"],
"default": "continuous"
},
"showTicks": "bool",
"min": "number",
"max": "number",
"value": "number",
"step": "number",
"label": "string",
"disabled": "bool"
},
"tags": []
}
}

View File

@ -1,59 +0,0 @@
{
"name": "@budibase/materialdesign-components",
"svelte": "src/index.svelte",
"main": "dist/index.js",
"module": "dist/index.js",
"scripts": {
"build": "rollup -c",
"prepublishOnly": "npm run build",
"testbuild": "rollup -w -c rollup.testconfig.js",
"dev": "run-p start:dev testbuild",
"start:dev": "sirv public --single --dev",
"dev:builder": "rollup -cw"
},
"devDependencies": {
"@budibase/client": "^0.1.17",
"@budibase/standard-components": "^0.1.17",
"@material/button": "^4.0.0",
"@material/checkbox": "^4.0.0",
"@material/data-table": "4.0.0",
"@material/dialog": "4.0.0",
"@material/form-field": "^4.0.0",
"@material/icon-button": "4.0.0",
"@material/list": "4.0.0",
"@material/menu": "4.0.0",
"@material/radio": "^4.0.0",
"@material/select": "4.0.0",
"@material/slider": "4.0.0",
"@material/switch": "4.0.0",
"@material/textfield": "^4.0.0",
"@nx-js/compiler-util": "^2.0.0",
"bcryptjs": "^2.4.3",
"date-fns": "^2.10.0",
"fs-extra": "^8.1.0",
"lodash": "^4.17.15",
"npm-run-all": "^4.1.5",
"rollup": "^1.11.0",
"rollup-plugin-alias": "^2.2.0",
"rollup-plugin-commonjs": "^10.0.2",
"rollup-plugin-json": "^4.0.0",
"rollup-plugin-livereload": "^1.0.1",
"rollup-plugin-node-resolve": "^5.0.0",
"rollup-plugin-postcss": "^2.0.5",
"rollup-plugin-svelte": "^5.0.0",
"rollup-plugin-terser": "^5.1.1",
"sass": "^1.25.1-test.1",
"shortid": "^2.2.15",
"sirv-cli": "^0.4.4",
"svelte": "^3.12.1"
},
"keywords": [
"svelte"
],
"version": "0.1.17",
"license": "MIT",
"gitHead": "284cceb9b703c38566c6e6363c022f79a08d5691",
"dependencies": {
"@material/card": "4.0.0"
}
}

View File

@ -1,16 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset='utf8'>
<meta name='viewport' content='width=device-width'>
<title>Budibase-Material Design</title>
<link rel='icon' type='image/png' href='/favicon.png'>
<link rel='stylesheet' href='/build/bundle.css'>
</head>
<body>
<script src='/build/bundle.js'></script>
</body>
</html>

View File

@ -1,50 +0,0 @@
import svelte from "rollup-plugin-svelte"
import postcss from "rollup-plugin-postcss"
import resolve from "rollup-plugin-node-resolve"
import commonjs from "rollup-plugin-commonjs"
const postcssOptions = () => ({
extensions: [".scss", ".sass"],
extract: false,
minimize: true,
use: [
[
"sass",
{
includePaths: ["./node_modules"],
},
],
],
})
const coreExternal = ["shortid"]
export default {
input: "src/index.js",
output: [
{
file: "dist/index.js",
format: "esm",
name: "budibaseStandardComponents",
sourcemap: true,
},
],
plugins: [
svelte({
hydratable: true,
}),
resolve({
preferBuiltins: true,
browser: true,
dedupe: importee => {
return coreExternal.includes(importee)
},
}),
commonjs({
namedExports: {
shortid: ["generate"],
},
}),
postcss(postcssOptions()),
],
}

View File

@ -1,166 +0,0 @@
import svelte from "rollup-plugin-svelte"
import resolve from "rollup-plugin-node-resolve"
import commonjs from "rollup-plugin-commonjs"
import livereload from "rollup-plugin-livereload"
import { terser } from "rollup-plugin-terser"
import json from "rollup-plugin-json"
import alias from "rollup-plugin-alias"
import postcss from "rollup-plugin-postcss"
import path from "path"
const aliases = {
resolve: [".js", ".svelte"],
entries: [
// { find: "@BBMD", replacement: path.resolve(__dirname, "dist/index.js") },
{ find: "@BBMD", replacement: path.resolve(__dirname, "src/index.js") },
],
}
const postcssOptions = () => ({
extensions: [".scss", ".sass"],
extract: false,
minimize: true,
use: [
[
"sass",
{
includePaths: ["./node_modules"],
},
],
],
})
const production = !process.env.ROLLUP_WATCH
const lodash_fp_exports = [
"find",
"isUndefined",
"split",
"max",
"last",
"union",
"reduce",
"isObject",
"cloneDeep",
"some",
"isArray",
"map",
"filter",
"keys",
"isFunction",
"isEmpty",
"countBy",
"join",
"includes",
"flatten",
"constant",
"first",
"intersection",
"take",
"has",
"mapValues",
"isString",
"isBoolean",
"isNull",
"isNumber",
"isObjectLike",
"isDate",
"clone",
"values",
"keyBy",
"isNaN",
"isInteger",
"toNumber",
]
const lodash_exports = [
"flow",
"head",
"find",
"each",
"tail",
"findIndex",
"startsWith",
"dropRight",
"takeRight",
"trim",
"split",
"replace",
"merge",
"assign",
]
const coreExternal = [
"lodash",
"lodash/fp",
"date-fns",
"lunr",
"safe-buffer",
"shortid",
"@nx-js/compiler-util",
"bcryptjs",
]
export default {
input: "src/Test/testMain.js",
output: {
sourcemap: true,
format: "iife",
name: "app",
file: "public/build/bundle.js",
globals: {
crypto: "crypto",
},
},
plugins: [
alias(aliases),
svelte({
// enable run-time checks when not in production
dev: !production,
// we'll extract any component CSS out into
// a separate file — better for performance
css: css => {
css.write("public/build/bundle.css")
},
hydratable: true,
}),
// If you have external dependencies installed from
// npm, you'll most likely need these plugins. In
// some cases you'll need additional configuration —
// consult the documentation for details:
// https://github.com/rollup/rollup-plugin-commonjs
resolve({
browser: true,
dedupe: importee => {
return (
importee === "svelte" ||
importee.startsWith("svelte/") ||
coreExternal.includes(importee)
)
},
preferBuiltins: true,
}),
commonjs({
namedExports: {
"lodash/fp": lodash_fp_exports,
lodash: lodash_exports,
shortid: ["generate"],
},
}),
json(),
// Watch the `public` directory and refresh the
// browser on changes when not in production
!production && livereload("public"),
// If we're building for production (npm run build
// instead of npm run dev), minify
production && terser(),
postcss(postcssOptions()),
],
watch: {
clearScreen: false,
},
}

View File

@ -1,74 +0,0 @@
<script>
import { onMount } from "svelte"
import Icon from "../Common/Icon.svelte"
import ripple from "../Common/Ripple.js"
import ClassBuilder from "../ClassBuilder.js"
const cb = new ClassBuilder("button", ["primary", "medium", "text"])
export let _bb
export let onClick
export let variant = "text"
export let colour = "primary"
export let size = "medium"
export let href = ""
export let icon = ""
export let trailingIcon = false
export let fullwidth = false
export let text = ""
export let disabled = false
onMount(() => {
let ctx = _bb.getContext("BBMD:button:context")
extras = [ctx]
})
let extras = ""
let modifiers = {}
let customs = { size, colour }
if (!href) modifiers = { variant }
$: props = { modifiers, customs, extras }
$: blockClasses = cb.build({ props })
const labelClass = cb.elem("label")
const clicked = () => _bb.call(onClick)
$: renderLeadingIcon = !!icon && !trailingIcon
$: renderTrailingIcon = !!icon && trailingIcon
</script>
{#if href}
<a class={blockClasses} {href} on:click={clicked}>
<span class={labelClass}>{text}</span>
</a>
{:else}
<button
use:ripple={{ colour }}
class={blockClasses}
class:fullwidth
{disabled}
on:click={clicked}>
{#if renderLeadingIcon}
<Icon context="button__icon" {icon} />
{/if}
<span class={labelClass}>{text}</span>
{#if renderTrailingIcon}
<Icon context="button__icon" {icon} />
{/if}
</button>
{/if}
<style>
.mdc-button:not(.fullwidth) {
width: fit-content;
}
.fullwidth {
width: 100%;
}
</style>

View File

@ -1,5 +0,0 @@
@import "@material/button/mdc-button.scss";
@import "@material/ripple/mdc-ripple.scss";
@import "./mixins.scss";
@include bbmd-button-styles();

View File

@ -1,39 +0,0 @@
@import "@material/feature-targeting/functions";
@import "@material/feature-targeting/mixins";
@mixin bbmd-button-styles($query: mdc-feature-all()) {
$feat-structure: mdc-feature-create-target($query, structure);
.bbmd-mdc-button--size-large {
@include button-sizing($feat-structure, 21px, 40px, 15px);
}
.bbmd-mdc-button--size-small {
@include button-sizing($feat-structure, 9px, 32px, 13px);
}
.bbmd-mdc-button--colour-secondary {
//no feature target as relying on theme variable custom property
@include mdc-button-ink-color(secondary);
&.mdc-button--raised,
&.mdc-button--unelevated {
@include mdc-button-container-fill-color(secondary);
@include mdc-button-ink-color(on-secondary);
}
&.mdc-button--outlined {
@include mdc-button-outline-color(secondary);
}
}
}
@mixin button-sizing($feat, $padding, $height, $fontSize) {
@include mdc-feature-targets($feat) {
padding: 0px $padding;
height: $height;
}
> .mdc-button__label {
font-size: $fontSize;
}
}

View File

@ -1,2 +0,0 @@
import "./_index.scss"
export { default as Button } from "./Button.svelte"

View File

@ -1,67 +0,0 @@
<script>
export let heading = ""
export let subheading = ""
export let content = ""
export let imageUrl = ""
export let button1Text = ""
export let button2Text = ""
export let cardClick = () => {}
export let button1Click = () => {}
export let button2Click = () => {}
$: showImage = !!imageUrl
$: showButton1 = !!button1Text
$: showButton2 = !!button2Text
$: showButtons = !!showButton1 && !!showButton2
</script>
<div class="mdc-card" on:click={cardClick}>
<div class="mdc-card__primary-action demo-card__primary-action" tabindex="0">
{#if showImage}
<div
class="mdc-card__media mdc-card__media--16-9 demo-card__media"
style="background-image: url(&quot;{imageUrl}&quot;);" />
{/if}
<div class="pad">
<div class="demo-card__primary">
<h2 class="demo-card__title mdc-typography mdc-typography--headline6">
{heading}
</h2>
<h3
class="demo-card__subtitle mdc-typography mdc-typography--subtitle2">
{subheading}
</h3>
</div>
<div class="demo-card__secondary mdc-typography mdc-typography--body2">
{content}
</div>
</div>
</div>
{#if showButtons}
<div class="mdc-card__actions">
<div class="mdc-card__action-buttons">
{#if showButton1}
<button class="mdc-button mdc-card__action mdc-card__action--button">
<span class="mdc-button__ripple" on:click={button1Click} />
{button1Text}
</button>
{/if}
{#if showButton2}
<button
class="mdc-button mdc-card__action mdc-card__action--button"
on:click={button2Click}>
<span class="mdc-button__ripple" />
{button2Text}
</button>
{/if}
</div>
</div>
{/if}
</div>
<style>
.pad {
padding: 10px;
}
</style>

View File

@ -1,39 +0,0 @@
<script>
import CardHeader from "./CardHeader.svelte"
import CardBody from "./CardBody.svelte"
import CardImage from "./CardImage.svelte"
import CardFooter from "./CardFooter.svelte"
import { H2, H6, Body2 } from "../Typography"
import { Button } from "../Button"
import { IconButton } from "../IconButton"
import ClassBuilder from "../ClassBuilder.js"
export let width = "350px"
export let height = "auto"
export let variant = "standard"
export let _bb
let card
const cb = new ClassBuilder("card", ["standard"])
$: modifiers = { variant }
$: props = { modifiers }
$: cardClass = cb.build({ props })
$: safeWidth = width !== "auto" && !/px$/.test(width) ? `${width}px` : width
$: safeHeight =
height !== "auto" && !/px$/.test(height) ? `${height}px` : height
$: card && _bb.attachChildren(card)
</script>
<div bind:this={card} class={`bbmd-card ${cardClass}`} />
<style>
.bbmd-card {
width: 350px;
height: auto;
}
</style>

View File

@ -1,12 +0,0 @@
<script>
export let _bb
export let onClick = () => {}
export let clicked = () => _bb.call(onClick)
let cardBody
$: cardBody && _bb.attachChildren(cardBody)
</script>
<div bind:this={cardBody} class="mdc-card__primary-action" on:click={clicked} />

View File

@ -1,31 +0,0 @@
<script>
import { onMount } from "svelte"
let cardFooter
export let _bb
export let padding = "5px"
onMount(() => {
_bb.setContext(
"BBMD:icon-button:context",
"mdc-card__action mdc-card__action--icon"
)
_bb.setContext(
"BBMD:button:context",
"mdc-card__action mdc-card__action--button"
)
})
$: cardFooter && _bb.attachChildren(cardFooter)
</script>
<div
bind:this={cardFooter}
style={`padding: ${padding}`}
class="mdc-card__actions bbmd-card__actions" />
<style>
.bbmd-card__actions {
display: flex;
flex-flow: row wrap;
}
</style>

View File

@ -1,44 +0,0 @@
<script>
import { H6, Sub2 } from "../Typography"
import Icon from "../Common/Icon.svelte"
export let _bb
export let title = ""
export let subtitle = ""
export let icon = ""
$: useIcon = !!icon
$: useSubtitle = !!subtitle
</script>
<div class="card-header">
{#if useIcon}
<div class="card-header__icon">
<Icon {icon} />
</div>
{/if}
<div class="card-header__title">
<H6 text={title} />
{#if useSubtitle}
<Sub2 text={subtitle} />
{/if}
</div>
</div>
<style>
.card-header {
display: flex;
flex-flow: row nowrap;
padding: 10px;
}
.card-header__icon {
flex: 0;
padding: 10px;
}
.card-header__title {
display: flex;
flex-flow: column nowrap;
flex: 1;
}
</style>

View File

@ -1,62 +0,0 @@
<script>
import { H6, Sub2 } from "../Typography"
export let _bb
export let displayHorizontal = false //aligns image on row with title and subtitle text
export let url = ""
export let title = ""
export let subtitle = ""
$: useTitle = !!title
$: useSubTitle = !!subtitle
</script>
{#if !displayHorizontal}
<div
class="my-card__media mdc-card__media mdc-card__media--16-9"
style={`background-image: url(${url})`}>
<div class="mdc-card__media-content bbmd-card-media__content">
{#if useTitle}
<H6 text={title} verticalMargin={0} horizontalMargin={10} />
{#if useSubTitle}
<Sub2 text={subtitle} horizontalMargin={10} />
{/if}
{/if}
</div>
</div>
{:else}
<div class="bbmd-card--horizontal">
<div
class="mdc-card__media mdc-card__media mdc-card__media--square
bbmd-card--horizontal-image"
style={`background-image: url(${url})`} />
<div class="bbmd-card--horizontal-text">
{#if useTitle}
<H6 text={title} verticalMargin={0} horizontalMargin={10} />
{#if useSubTitle}
<Sub2 text={subtitle} horizontalMargin={10} />
{/if}
{/if}
</div>
</div>
{/if}
<style>
.bbmd-card--horizontal {
display: flex;
flex-flow: row nowrap;
}
.bbmd-card--horizontal-image {
flex: 0 0 110px;
}
.bbmd-card--horizontal-text {
flex: 1;
padding: 10px 5px;
}
.bbmd-card-media__content {
top: 125px;
color: white;
}
</style>

View File

@ -1 +0,0 @@
@import "@material/card/mdc-card.scss"

View File

@ -1,7 +0,0 @@
import "./_styles.scss"
export { default as Card } from "./Card.svelte"
export { default as BasicCard } from "./BasicCard.svelte"
export { default as CardBody } from "./CardBody.svelte"
export { default as CardFooter } from "./CardFooter.svelte"
export { default as CardHeader } from "./CardHeader.svelte"
export { default as CardImage } from "./CardImage.svelte"

View File

@ -1,146 +0,0 @@
<script>
import { onMount, onDestroy } from "svelte"
import Formfield from "../Common/Formfield.svelte"
import { fieldStore } from "../Common/FormfieldStore.js"
import ClassBuilder from "../ClassBuilder.js"
import { MDCCheckbox } from "@material/checkbox"
import { generate } from "shortid"
export let onChange = item => {}
export let _bb
export let id = ""
export let value = null
export let label = ""
export let disabled = false
export let alignEnd = false
export let indeterminate = false
export let checked = false
let _id
let instance = null
let checkbox = null
let selectedItems
let checkProps
let context = _bb.getContext("BBMD:input:context")
onMount(() => {
_id = generate()
if (!!checkbox) {
instance = new MDCCheckbox(checkbox)
instance.indeterminate = indeterminate
if (context !== "list-item") {
let fieldStore = _bb.getContext("BBMD:field-element")
if (fieldStore) fieldStore.setInput(instance)
}
}
if (context === "checkboxgroup") {
selectedItems = _bb.getContext("BBMD:checkbox:selectedItemsStore")
checkProps = _bb.getContext("BBMD:checkbox:props")
}
})
let extras = null
if (context === "list-item") {
extras = ["mdc-list-item__meta"]
}
const cb = new ClassBuilder("checkbox")
let modifiers = { disabled }
let props = { modifiers, extras }
const blockClass = cb.build({ props })
function changed(e) {
const val = e.target.checked
checked = val
handleOnClick()
if (_bb.isBound(_bb.props.checked)) {
_bb.setStateFromBinding(_bb.props.checked, val)
}
}
function handleOnClick() {
let item = { _id, checked, label, value }
if (context === "checkboxgroup") {
let idx = selectedItems.getItemIdx($selectedItems, _id)
if (idx > -1) {
selectedItems.removeItem(_id)
} else {
selectedItems.addItem(item)
}
}
_bb.call(onChange, item)
}
$: isChecked =
context === "checkboxgroup"
? $selectedItems && $selectedItems.findIndex(i => i._id === _id) > -1
: checked
$: isAlignedEnd =
context === "checkboxgroup" && !!checkProps ? checkProps.alignEnd : alignEnd
$: isDisabled =
context === "checkboxgroup" && !!checkProps ? checkProps.disabled : disabled
</script>
<!-- TODO: Customizing Colour and Density - What level of customization for these things does Budibase need here? -->
{#if context !== 'list-item'}
<div class="bbmd-checkbox">
<Formfield {label} {_bb} {id} alignEnd={isAlignedEnd}>
<div bind:this={checkbox} class={blockClass}>
<input
type="checkbox"
class={cb.elem`native-control`}
{id}
disabled={isDisabled}
checked={isChecked}
on:change={changed} />
<div class={cb.elem`background`}>
<svg class={cb.elem`checkmark`} viewBox="0 0 24 24">
<path
class={cb.elem`checkmark-path`}
fill="none"
d="M1.73,12.91 8.1,19.28 22.79,4.59" />
</svg>
<div class={cb.elem`mixedmark`} />
</div>
<div class={cb.elem`ripple`} />
</div>
</Formfield>
</div>
{:else}
<div bind:this={checkbox} class={blockClass}>
<input
type="checkbox"
class={cb.elem`native-control`}
{id}
disabled={isDisabled}
checked={isChecked}
on:change={changed} />
<div class={cb.elem`background`}>
<svg class={cb.elem`checkmark`} viewBox="0 0 24 24">
<path
class={cb.elem`checkmark-path`}
fill="none"
d="M1.73,12.91 8.1,19.28 22.79,4.59" />
</svg>
<div class={cb.elem`mixedmark`} />
</div>
<div class={cb.elem`ripple`} />
</div>
{/if}
<style>
.bbmd-checkbox {
width: fit-content;
}
</style>

View File

@ -1,69 +0,0 @@
<script>
import { onMount } from "svelte"
import Checkbox from "./Checkbox.svelte"
import Label from "../Common/Label.svelte"
import createItemsStore from "../Common/ItemStore.js"
let selectedItemsStore
let checkItems
export let _bb
export let label = ""
export let orientation = "row"
export let onChange = selectedItems => {}
export let disabled = false
export let alignEnd = false
export let value = []
onMount(() => {
_bb.setContext("BBMD:input:context", "checkboxgroup")
selectedItemsStore = createItemsStore(() => {
value = $selectedItemsStore
if (_bb.isBound(_bb.props.value)) {
_bb.setStateFromBinding(_bb.props.value, value)
}
_bb.call(onChange, value)
}, value)
_bb.setContext("BBMD:checkbox:selectedItemsStore", selectedItemsStore)
_bb.setContext("BBMD:checkbox:props", { alignEnd, disabled })
})
$: checkItems && _bb.attachChildren(checkItems)
</script>
<div class="checkbox-group">
<div class="checkbox-group__label">
<Label text={label} bold />
</div>
<div bind:this={checkItems} class={`checkbox-group__boxes ${orientation}`} />
</div>
<style>
.checkbox-group {
display: flex;
flex-direction: column;
width: fit-content;
}
.checkbox-group__boxes.row > div:not(:first-child) {
padding-left: 10px;
}
.checkbox-group > div {
text-align: left;
flex: 1;
}
.row {
display: flex;
flex-flow: row wrap;
align-items: flex-start;
}
.column {
display: flex;
flex-flow: column wrap;
align-items: flex-start;
}
</style>

View File

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

View File

@ -1,3 +0,0 @@
import "./_style.scss"
export { default as Checkbox } from "./Checkbox.svelte"
export { default as Checkboxgroup } from "./CheckboxGroup.svelte"

View File

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

View File

@ -1,7 +0,0 @@
<script>
import "@material/floating-label/mdc-floating-label.scss"
export let forInput = ""
export let text = ""
</script>
<label for={forInput} class="mdc-floating-label">{text}</label>

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More