Add new data bindings for context bindings and most work for component bindings

This commit is contained in:
Andrew Kingston 2021-01-14 15:39:50 +00:00
parent bd237b3e7c
commit 5211d316f8
17 changed files with 415 additions and 542 deletions

View File

@ -1,4 +1,4 @@
packages/builder/src/userInterface/CurrentItemPreview.svelte
packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte
public
dist
packages/server/builder

View File

@ -0,0 +1,97 @@
import { get } from "svelte/store"
import { backendUiStore, store } from "builderStore"
import { findAllMatchingComponents, findComponentPath } from "./storeUtils"
/**
* Gets all bindable data context fields and instance fields.
*/
export const getBindableProperties = (rootComponent, componentId) => {
const bindableContexts = getBindableContexts(rootComponent, componentId)
const bindableComponents = getBindableComponents(rootComponent)
return [...bindableContexts, ...bindableComponents]
}
/**
* Gets all bindable data contexts. These are fields of schemas of data contexts
* provided by data provider components, such as lists or row detail components.
*/
export const getBindableContexts = (rootComponent, componentId) => {
if (!rootComponent || !componentId) {
return []
}
// Get the component tree leading up to this component
const path = findComponentPath(rootComponent, componentId)
path.pop()
// Extract any components which provide data contexts
const dataProviders = path.filter(component => {
const def = store.actions.components.getDefinition(component._component)
return def?.dataProvider
})
let contexts = []
dataProviders.forEach(provider => {
if (!provider.datasource) {
return
}
const { schema, table } = getSchemaForDatasource(provider.datasource)
Object.entries(schema).forEach(([key, schema]) => {
// Replace certain bindings with a new property to help display components
let runtimeBoundKey = key
if (schema.type === "link") {
runtimeBoundKey = `${key}_count`
} else if (schema.type === "attachment") {
runtimeBoundKey = `${key}_first`
}
contexts.push({
type: "context",
runtimeBinding: `${provider._id}.${runtimeBoundKey}`,
readableBinding: `${provider._instanceName}.${table.name}.${key}`,
fieldSchema: schema,
providerId: provider._id,
})
})
})
return contexts
}
/**
* Gets all bindable components. These are form components which allow their
* values to be bound to.
*/
export const getBindableComponents = rootComponent => {
if (!rootComponent) {
return []
}
const componentSelector = component => {
const type = component._component
const definition = store.actions.components.getDefinition(type)
return definition.bindable
}
const components = findAllMatchingComponents(rootComponent, componentSelector)
console.log(components)
return []
}
/**
* Gets a schema for a datasource object.
*/
const getSchemaForDatasource = datasource => {
const tables = get(backendUiStore).tables
const { type } = datasource
const table = tables.find(table => table._id === datasource.tableId)
let schema = {}
if (table) {
if (type === "table") {
schema = table.schema ?? {}
} else if (type === "view") {
schema = table.views?.[datasource.name]?.schema ?? {}
} else if (type === "link") {
schema = table.schema ?? {}
}
}
return { schema, table }
}

View File

@ -1,48 +0,0 @@
import { walkProps } from "./storeUtils"
import { get_capitalised_name } from "../helpers"
import { get } from "svelte/store"
import { allScreens } from "builderStore"
import { FrontendTypes } from "../constants"
import { currentAsset } from "."
export default function(component, state) {
const capitalised = get_capitalised_name(
component.name || component._component
)
const matchingComponents = []
const findMatches = props => {
walkProps(props, c => {
const thisInstanceName = get_capitalised_name(c._instanceName)
if ((thisInstanceName || "").startsWith(capitalised)) {
matchingComponents.push(thisInstanceName)
}
})
}
// check layouts first
for (let layout of state.layouts) {
findMatches(layout.props)
}
// if viewing screen, check current screen for duplicate
if (state.currentFrontEndType === FrontendTypes.SCREEN) {
findMatches(get(currentAsset).props)
} else {
// viewing a layout - need to find against all screens
for (let screen of get(allScreens)) {
findMatches(screen.props)
}
}
let index = 1
let name
while (!name) {
const tryName = `${capitalised || "Copy"} ${index}`
if (!matchingComponents.includes(tryName)) name = tryName
index++
}
return name
}

View File

@ -30,9 +30,10 @@ export function runtimeToReadableBinding(bindableProperties, textWithBindings) {
const binding = bindableProperties.find(({ runtimeBinding }) => {
return v === `{{ ${runtimeBinding} }}`
})
if (binding) {
temp = temp.replace(v, `{{ ${binding.readableBinding} }}`)
}
temp = temp.replace(
v,
`{{ ${binding?.readableBinding ?? "Invalid binding"} }}`
)
})
return temp

View File

@ -497,7 +497,10 @@ export const getFrontendStore = () => {
const path = findComponentPath(selectedAsset.props, component._id) || []
// Remove root entry since it's the screen or layout
return path.slice(1).join("/")
return path
.slice(1)
.map(component => component._id)
.join("/")
},
links: {
save: async (url, title) => {

View File

@ -40,22 +40,45 @@ export const findComponentParent = (rootComponent, id, parentComponent) => {
*/
export const findComponentPath = (rootComponent, id, path = []) => {
if (!rootComponent || !id) {
return null
return []
}
if (rootComponent._id === id) {
return [...path, id]
return [...path, rootComponent]
}
if (!rootComponent._children) {
return null
return []
}
for (const child of rootComponent._children) {
const newPath = [...path, rootComponent._id]
const newPath = [...path, rootComponent]
const childResult = findComponentPath(child, id, newPath)
if (childResult != null) {
if (childResult?.length) {
return childResult
}
}
return null
return []
}
/**
* Recurses through the component tree and finds all components of a certain
* type.
*/
export const findAllMatchingComponents = (rootComponent, selector) => {
if (!rootComponent || !selector) {
return []
}
let components = []
if (rootComponent._children) {
rootComponent._children.forEach(child => {
components = [
...components,
...findAllMatchingComponents(child, selector),
]
})
}
if (selector(rootComponent)) {
components.push(rootComponent)
}
return components.reverse()
}
/**

View File

@ -1,12 +1,32 @@
[
"container",
"grid",
"list",
{
"name": "Category",
"name": "Cards",
"icon": "ri-file-edit-line",
"children": [
"container"
"card",
"stackedlist"
]
},
"grid",
"screenslot",
"button"
{
"name": "Forms",
"icon": "ri-file-edit-line",
"children": [
"form",
"input",
"richtext",
"datepicker"
]
},
"button",
"text",
{
"name": "Other",
"icon": "ri-file-edit-line",
"children": [
"screenslot"
]
}
]

View File

@ -77,18 +77,16 @@ export default function() {
// Stop if the target and source are the same
if (state.targetComponent === state.dragged) {
console.log("same component")
return
}
// Stop if the target or source are null
if (!state.targetComponent || !state.dragged) {
console.log("null component")
return
}
// Stop if the target is a child of source
const path = findComponentPath(state.dragged, state.targetComponent._id)
if (path?.includes(state.targetComponent._id)) {
console.log("target is child of course")
const ids = path.map(component => component._id)
if (ids.includes(state.targetComponent._id)) {
return
}

View File

@ -1,8 +1,8 @@
<script>
import { Icon } from "@budibase/bbui"
import Input from "./Input.svelte"
import { store, backendUiStore, currentAsset } from "builderStore"
import fetchBindableProperties from "builderStore/fetchBindableProperties"
import { store, currentAsset } from "builderStore"
import { getBindableProperties } from "builderStore/dataBinding"
import {
readableToRuntimeBinding,
runtimeToReadableBinding,
@ -15,67 +15,48 @@
export let componentInstance = {}
export let control = null
export let key = ""
export let value
export let type = ""
export let value = null
export let props = {}
export let onChange = () => {}
let temporaryBindableValue = value
let bindableProperties = []
let anchor
let dropdown
function handleClose() {
handleChange(key, temporaryBindableValue)
}
$: bindableProperties = getBindableProperties(
$currentAsset.props,
$store.selectedComponentId
)
$: safeValue = getSafeValue(value, props.defaultValue, bindableProperties)
$: replaceBindings = val => readableToRuntimeBinding(bindableProperties, val)
function getBindableProperties() {
// Get all bindableProperties
bindableProperties = fetchBindableProperties({
componentInstanceId: $store.selectedComponentId,
components: $store.components,
screen: $currentAsset,
tables: $backendUiStore.tables,
})
}
function replaceBindings(textWithBindings) {
getBindableProperties()
textWithBindings = readableToRuntimeBinding(
bindableProperties,
textWithBindings
)
onChange(key, textWithBindings)
}
function handleChange(key, v) {
let innerVal = v
if (typeof v === "object") {
if ("detail" in v) {
innerVal = v.detail
} else if ("target" in v) {
innerVal = props.valueKey ? v.target[props.valueKey] : v.target.value
// Handle a value change of any type
// String values have any bindings handled
const handleChange = value => {
let innerVal = value
if (value && typeof value === "object") {
if ("detail" in value) {
innerVal = value.detail
} else if ("target" in value) {
innerVal = value.target.value
}
}
if (typeof innerVal === "string") {
replaceBindings(innerVal)
onChange(replaceBindings(innerVal))
} else {
onChange(key, innerVal)
onChange(innerVal)
}
}
const safeValue = () => {
getBindableProperties()
let temp = runtimeToReadableBinding(bindableProperties, value)
return value == null && props.initialValue !== undefined
? props.initialValue
: temp
// The "safe" value is the value with eny bindings made readable
// If there is no value set, any default value is used
const getSafeValue = (value, defaultValue, bindableProperties) => {
const enriched = runtimeToReadableBinding(bindableProperties, value)
return enriched == null && defaultValue !== undefined
? defaultValue
: enriched
}
// Incase the component has a different value key name
const handlevalueKey = value =>
props.valueKey ? { [props.valueKey]: safeValue() } : { value: safeValue() }
</script>
<div class="property-control" bind:this={anchor}>
@ -84,13 +65,13 @@
<svelte:component
this={control}
{componentInstance}
{...handlevalueKey(value)}
on:change={val => handleChange(key, val)}
onChange={val => handleChange(key, val)}
value={safeValue}
on:change={handleChange}
onChange={handleChange}
{...props}
name={key} />
</div>
{#if bindable && !key.startsWith('_') && control === Input}
{#if bindable && !key.startsWith('_') && type === 'text'}
<div
class="icon"
data-cy={`${key}-binding-button`}
@ -99,14 +80,14 @@
</div>
{/if}
</div>
{#if control == Input}
{#if type === 'text'}
<DropdownMenu
on:close={handleClose}
on:close={() => handleChange(temporaryBindableValue)}
bind:this={dropdown}
{anchor}
align="right">
<BindingDropdown
{...handlevalueKey(value)}
value={safeValue}
close={dropdown.hide}
on:update={e => (temporaryBindableValue = e.detail)}
{bindableProperties} />

View File

@ -22,11 +22,12 @@
<div>
{#each properties as prop}
<PropertyControl
bindable={false}
label={`${prop.label}${hasPropChanged(style, prop) ? ' *' : ''}`}
control={prop.control}
key={prop.key}
value={style[prop.key]}
onChange={(key, value) => onStyleChanged(styleCategory, key, value)}
onChange={value => onStyleChanged(styleCategory, prop.key, value)}
props={{ options: prop.options, placeholder: prop.placeholder }} />
{/each}
</div>

View File

@ -2,7 +2,7 @@
import { Icon, DropdownMenu, Heading } from "@budibase/bbui"
import { createEventDispatcher } from "svelte"
import { store, backendUiStore, currentAsset } from "builderStore"
import fetchBindableProperties from "builderStore/fetchBindableProperties"
import { getBindableProperties } from "builderStore/dataBinding"
const dispatch = createEventDispatcher()
let anchorRight, dropdownRight
@ -31,18 +31,17 @@
return [...acc, ...viewsArr]
}, [])
$: bindableProperties = fetchBindableProperties({
componentInstanceId: $store.selectedComponentId,
components: $store.components,
screen: $currentAsset,
tables: $backendUiStore.tables,
})
$: bindableProperties = getBindableProperties(
$currentAsset.props,
$store.selectedComponentId
)
$: console.log(bindableProperties)
$: links = bindableProperties
.filter(x => x.fieldSchema?.type === "link")
.map(property => {
return {
providerId: property.instance._id,
providerId: property.providerId,
label: property.readableBinding,
fieldName: property.fieldSchema.name,
name: `all_${property.fieldSchema.tableId}`,

View File

@ -25,6 +25,13 @@
export let onScreenPropChange = () => {}
export let showDisplayName = false
const layoutDefinition = []
const screenDefinition = [
{ key: "description", label: "Description", control: Input },
{ key: "routing.route", label: "Route", control: Input },
{ key: "routing.roleId", label: "Access", control: RoleSelect },
{ key: "layoutId", label: "Layout", control: LayoutSelect },
]
const assetProps = [
"title",
"description",
@ -34,12 +41,15 @@
]
$: settings = componentDefinition?.settings ?? []
$: isLayout = assetInstance && assetInstance.favicon
$: assetDefinition = isLayout ? layoutDefinition : screenDefinition
const controlMap = {
text: Input,
select: OptionSelect,
datasource: TableViewSelect,
detailURL: DetailScreenSelect,
screen: ScreenSelect,
detailScreen: DetailScreenSelect,
boolean: Checkbox,
number: Input,
event: EventsEditor,
@ -48,17 +58,6 @@
return controlMap[type]
}
const propExistsOnComponentDef = prop =>
assetProps.includes(prop) || prop in (componentDefinition?.props ?? {})
const layoutDefinition = []
const screenDefinition = [
{ key: "description", label: "Description", control: Input },
{ key: "routing.route", label: "Route", control: Input },
{ key: "routing.roleId", label: "Access", control: RoleSelect },
{ key: "layoutId", label: "Layout", control: LayoutSelect },
]
const canRenderControl = setting => {
const control = getControl(setting?.type)
if (!control) {
@ -70,29 +69,27 @@
return true
}
$: isLayout = assetInstance && assetInstance.favicon
$: assetDefinition = isLayout ? layoutDefinition : screenDefinition
const onInstanceNameChange = (_, name) => {
const onInstanceNameChange = name => {
onChange("_instanceName", name)
}
</script>
<div class="settings-view-container">
{#if assetInstance}
{#each assetDefinition as def}
{#each assetDefinition as def (`${componentInstance._id}-${def.key}`)}
<PropertyControl
bindable={false}
control={def.control}
label={def.label}
key={def.key}
value={get(assetInstance, def.key)}
onChange={onScreenPropChange} />
onChange={val => onScreenPropChange(def.key, val)} />
{/each}
{/if}
{#if showDisplayName}
<PropertyControl
bindable={false}
control={Input}
label="Name"
key="_instanceName"
@ -101,15 +98,16 @@
{/if}
{#if settings && settings.length > 0}
{#each settings as setting}
{#each settings as setting (`${componentInstance._id}-${setting.key}`)}
{#if canRenderControl(setting)}
<PropertyControl
type={setting.type}
control={getControl(setting.type)}
label={setting.label}
key={setting.key}
value={componentInstance[setting.key] ?? componentInstance[setting.key]?.defaultValue}
{componentInstance}
{onChange}
onChange={val => onChange(setting.key, val)}
props={{ options: setting.options }} />
{/if}
{/each}

View File

@ -1,225 +1,10 @@
import Input from "./PropertiesPanel/PropertyControls/Input.svelte"
import OptionSelect from "./PropertiesPanel/PropertyControls/OptionSelect.svelte"
import MultiTableViewFieldSelect from "./PropertiesPanel/PropertyControls/MultiTableViewFieldSelect.svelte"
import Checkbox from "../common/Checkbox.svelte"
import TableSelect from "components/userInterface/PropertyControls/TableSelect.svelte"
import TableViewSelect from "components/userInterface/PropertyControls/TableViewSelect.svelte"
import TableViewFieldSelect from "components/userInterface/PropertyControls/TableViewFieldSelect.svelte"
import Event from "components/userInterface/PropertyControls/EventsEditor/EventPropertyControl.svelte"
import ScreenSelect from "components/userInterface/PropertyControls/ScreenSelect.svelte"
import DetailScreenSelect from "components/userInterface/PropertyControls/DetailScreenSelect.svelte"
import { IconSelect } from "components/userInterface/PropertyControls/IconSelect"
import Colorpicker from "@budibase/colorpicker"
import { all } from "./propertyCategories.js"
/*
{ label: "N/A ", value: "N/A" },
{ label: "Flex", value: "flex" },
{ label: "Inline Flex", value: "inline-flex" },
*/
export default {
categories: [
{
name: "Repeater",
_component: "@budibase/standard-components/list",
description: "Renders all children once per row, of a given table",
icon: "ri-list-check-2",
properties: {
design: { ...all },
settings: [
{
label: "Data",
key: "datasource",
control: TableViewSelect,
},
],
},
children: [],
},
{
name: "Form",
icon: "ri-file-edit-line",
isCategory: true,
children: [
{
_component: "@budibase/standard-components/form",
name: "Form",
icon: "ri-file-edit-line",
properties: {
design: { ...all },
settings: [],
},
},
{
_component: "@budibase/standard-components/textfield",
name: "Text Field",
description:
"A textfield component that allows the user to input text.",
icon: "ri-edit-box-line",
properties: {
design: { ...all },
settings: [
{ label: "Label", key: "label", control: Input },
{
label: "Type",
key: "type",
control: OptionSelect,
options: ["text", "password"],
},
],
},
},
// {
// _component: "@budibase/standard-components/richtext",
// name: "Rich Text",
// description:
// "A component that allows the user to enter long form text.",
// icon: "ri-edit-box-line",
// properties: {
// design: { ...all },
// settings: [],
// },
// },
// {
// _component: "@budibase/standard-components/datepicker",
// name: "Date Picker",
// description: "A basic date picker component",
// icon: "ri-calendar-line",
// children: [],
// properties: {
// design: { ...all },
// settings: [
// { label: "Placeholder", key: "placeholder", control: Input },
// ],
// },
// },
],
},
{
name: "Card",
icon: "ri-archive-drawer-line",
isCategory: true,
children: [
{
_component: "@budibase/standard-components/stackedlist",
name: "Stacked List",
icon: "ri-archive-drawer-line",
description:
"A basic card component that can contain content and actions.",
children: [],
properties: {
design: { ...all },
settings: [
{
label: "Image",
key: "imageUrl",
control: Input,
placeholder: "{{{context.Image}}}",
},
{
label: "Heading",
key: "heading",
control: Input,
placeholder: "{{context.Heading}}",
},
{
label: "Text 1",
key: "text1",
control: Input,
placeholder: "{{context.Text 1}}",
},
{
label: "Text 2",
key: "text2",
control: Input,
placeholder: "{{context.Text 2}}",
},
{
label: "Text 3",
key: "text3",
control: Input,
placeholder: "{{context.Text 3}}",
},
{
label: "Link URL",
key: "destinationUrl",
control: ScreenSelect,
placeholder: "/table/_id",
},
],
},
},
{
_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: ScreenSelect,
placeholder: "Link URL",
},
{
label: "Link Color",
key: "linkColor",
control: Colorpicker,
defaultValue: "#000",
},
{
label: "Hover Color",
key: "linkHoverColor",
control: Colorpicker,
defaultValue: "#222",
},
{
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",
},
],
},
},
{
_component: "@budibase/standard-components/cardhorizontal",
name: "Horizontal",
@ -957,40 +742,7 @@ export default {
],
},
},
{
_component: "@budibase/standard-components/text",
name: "Paragraph",
description: "A component for displaying paragraph text.",
icon: "ri-paragraph",
properties: {
design: { ...all },
settings: [
{
label: "Text",
key: "text",
control: Input,
},
{
label: "Type",
key: "type",
control: OptionSelect,
options: [
"none",
"bold",
"strong",
"italic",
"emphasis",
"mark",
"small",
"del",
"ins",
"sub",
"sup",
],
},
],
},
},
{
_component: "@budibase/standard-components/image",
name: "Image",

View File

@ -36,57 +36,6 @@
"logon"
]
},
"input": {
"name": "Textfield",
"bindable": "value",
"description": "An HTML input",
"props": {
"type": {
"type": "options",
"options": [
"text",
"password",
"checkbox",
"color",
"date",
"datetime-local",
"email",
"file",
"hidden",
"image",
"month",
"number",
"radio",
"range",
"reset",
"search",
"submit",
"tel",
"time",
"week"
],
"default": "text"
}
},
"tags": [
"form"
]
},
"text": {
"name": "Text",
"description": "stylable block of text",
"props": {
"text": "string",
"type": {
"type": "string",
"default": "none"
}
},
"tags": [
"div",
"container"
]
},
"richtext": {
"name": "Rich Text",
"description": "A component that allows the user to enter long form text.",
@ -109,40 +58,7 @@
}
},
"dataform": {
"name": "Form",
"description": "an HTML table that fetches data from a table or view and displays it.",
"data": true,
"props": {}
},
"dataformwide": {
"name": "Form Wide",
"description": "an HTML table that fetches data from a table or view and displays it.",
"data": true,
"props": {}
},
"list": {
"name": "Repeater",
"description": "A configurable data list that attaches to your backend tables.",
"context": "datasource",
"children": true,
"data": true,
"props": {
"datasource": "tables"
}
},
"stackedlist": {
"name": "Stacked List",
"description": "A stacked list component for displaying information",
"props": {
"imageUrl": "string",
"heading": "string",
"text1": "string",
"text2": "string",
"text3": "string",
"destinationUrl": "string"
}
},
"rowdetail": {
"name": "Row Detail",
"description": "Loads a row, using an ID in the url",
@ -165,43 +81,6 @@
"table": "tables"
}
},
"card": {
"name": "Card",
"props": {
"imageUrl": "string",
"heading": "string",
"description": "string",
"linkText": "string",
"linkUrl": "string",
"linkColor": {
"type": "string",
"default": "#000"
},
"linkHoverColor": {
"type": "string",
"default": "#000"
},
"imageHeight": {
"type": "options",
"default": "20rem",
"options": [
"12rem",
"16rem",
"20rem",
"24rem"
]
},
"cardWidth": {
"type": "options",
"default": "20rem",
"options": [
"16rem",
"20rem",
"24rem"
]
}
}
},
"cardstat": {
"name": "Stat Card",
"props": {

View File

@ -42,7 +42,7 @@
"key": "datasource"
},
{
"type": "detailURL",
"type": "detailScreen",
"label": "Detail URL",
"key": "detailUrl"
},
@ -90,8 +90,7 @@
{
"type": "boolean",
"label": "Disabled",
"key": "disabled",
"valueKey": "checked"
"key": "disabled"
},
{
"type": "event",
@ -99,5 +98,176 @@
"key": "onClick"
}
]
},
"list": {
"name": "Repeater",
"description": "A configurable data list that attaches to your backend tables.",
"icon": "ri-list-check-2",
"styleable": true,
"hasChildren": true,
"dataProvider": true,
"settings": [
{
"type": "datasource",
"label": "Data",
"key": "datasource"
}
]
},
"form": {
"name": "Form",
"icon": "ri-file-edit-line",
"styleable": true
},
"input": {
"name": "Text Field",
"description": "A textfield component that allows the user to input text.",
"icon": "ri-edit-box-line",
"styleable": true,
"bindable": true,
"settings": [
{
"type": "text",
"label": "Label",
"key": "label"
},
{
"label": "Type",
"key": "type",
"defaultValue": "text",
"options": ["text", "password"]
}
]
},
"richtext": {
"name": "Rich Text",
"description": "A component that allows the user to enter long form text.",
"icon": "ri-edit-box-line",
"styleable": true,
"bindable": true
},
"datepicker": {
"name": "Date Picker",
"description": "A basic date picker component",
"icon": "ri-calendar-line",
"styleable": true,
"bindable": true,
"settings": [
{
"type": "text",
"label": "Placeholder",
"key": "placeholder"
}
]
},
"stackedlist": {
"name": "Stacked List",
"icon": "ri-archive-drawer-line",
"description": "A basic card component that can contain content and actions.",
"styleable": true,
"settings": [
{
"type": "text",
"label": "Image",
"key": "imageUrl"
},
{
"type": "text",
"label": "Heading",
"key": "heading"
},
{
"type": "text",
"label": "Text 1",
"key": "text1"
},
{
"type": "text",
"label": "Text 2",
"key": "text2"
},
{
"type": "text",
"label": "Text 3",
"key": "text3"
},
{
"type": "screen",
"label": "Link URL",
"key": "destinationUrl"
}
]
},
"card": {
"name": "Vertical Card",
"description": "A basic card component that can contain content and actions.",
"icon": "ri-layout-column-line",
"styleable": true,
"settings": [
{
"type": "text",
"label": "Image",
"key": "imageUrl"
},
{
"type": "text",
"label": "Heading",
"key": "heading"
},
{
"type": "text",
"label": "Description",
"key": "description"
},
{
"type": "text",
"label": "Link Text",
"key": "linkText"
},
{
"type": "screen",
"label": "Link Url",
"key": "linkUrl"
},
{
"type": "color",
"label": "Link Color",
"key": "linkColor",
"defaultValue": "#000"
},
{
"type": "color",
"label": "Hover Color",
"key": "linkHoverColor",
"defaultValue": "#222"
},
{
"type": "select",
"label": "Image Height",
"key": "imageHeight",
"options": ["auto", "12rem", "16rem", "20rem", "24rem"],
"defaultValue": "auto"
},
{
"type": "select",
"label": "Card Width",
"key": "cardWidth",
"options": ["16rem", "20rem", "24rem"],
"defaultValue": "20rem"
}
]
},
"text": {
"name": "Paragraph",
"description": "A component for displaying paragraph text.",
"icon": "ri-paragraph",
"styleable": true,
"settings": [
{
"type": "text",
"label": "Text",
"key": "text"
}
]
}
}

View File

@ -1,6 +1,5 @@
<script>
import { getContext } from "svelte"
import { cssVars } from "./helpers"
const { styleable } = getContext("sdk")
const component = getContext("component")
@ -16,22 +15,21 @@
export let imageHeight
export let cardWidth
$: cssVariables = {
imageHeight,
cardWidth,
$: cardStyles = {
...$component.styles,
normal: {
...$component.styles.normal,
width: cardWidth,
},
}
$: showImage = !!imageUrl
</script>
<div
use:cssVars={cssVariables}
class="container"
use:styleable={$component.styles}
style="--cardWidth: {cardWidth}">
<div class="container" use:styleable={cardStyles}>
{#if showImage}
<img
style="--imageWidth: {imageWidth}; --imageHeight: {imageHeight}"
style="--imageHeight: {imageHeight}"
class="image"
src={imageUrl}
alt="" />

View File

@ -9,23 +9,24 @@ export { default as container } from "./Container.svelte"
export { default as grid } from "./grid/Component.svelte"
export { default as screenslot } from "./ScreenSlot.svelte"
export { default as button } from "./Button.svelte"
export { default as input } from "./Input.svelte"
export { default as richtext } from "./RichText.svelte"
export { default as list } from "./List.svelte"
export { default as stackedlist } from "./StackedList.svelte"
export { default as card } from "./Card.svelte"
export { default as form } from "./DataFormWide.svelte"
export { default as datepicker } from "./DatePicker.svelte"
export { default as text } from "./Text.svelte"
// export { default as text } from "./Text.svelte"
// export { default as heading } from "./Heading.svelte"
// export { default as input } from "./Input.svelte"
// export { default as richtext } from "./RichText.svelte"
// export { default as login } from "./Login.svelte"
// export { default as link } from "./Link.svelte"
// export { default as image } from "./Image.svelte"
// export { default as navigation } from "./Navigation.svelte"
// export { default as list } from "./List.svelte"
// export { default as embed } from "./Embed.svelte"
// export { default as stackedlist } from "./StackedList.svelte"
// export { default as card } from "./Card.svelte"
// export { default as cardhorizontal } from "./CardHorizontal.svelte"
// export { default as cardstat } from "./CardStat.svelte"
// export { default as rowdetail } from "./RowDetail.svelte"
// export { default as newrow } from "./NewRow.svelte"
// export { default as datepicker } from "./DatePicker.svelte"
// export { default as icon } from "./Icon.svelte"
// export * from "./charts"