Unify all popovers and modals to the same styles and reuse common components. Fix filter options

This commit is contained in:
Andrew Kingston 2020-09-25 11:35:32 +01:00
parent 86b475fe14
commit 6320a85bc2
17 changed files with 263 additions and 372 deletions

View File

@ -8,7 +8,7 @@
import LinkedRecord from "./LinkedRecord.svelte"
import AttachmentList from "./AttachmentList.svelte"
import TablePagination from "./TablePagination.svelte"
import { DeleteRecordModal, CreateEditRecordModal } from "./modals"
import { CreateEditRecordModal } from "./modals"
import RowPopover from "./popovers/Row.svelte"
import ColumnPopover from "./popovers/Column.svelte"
import ViewPopover from "./popovers/View.svelte"

View File

@ -8,7 +8,7 @@
import ActionButton from "components/common/ActionButton.svelte"
import AttachmentList from "./AttachmentList.svelte"
import TablePagination from "./TablePagination.svelte"
import { DeleteRecordModal, CreateEditRecordModal } from "./modals"
import { CreateEditRecordModal } from "./modals"
import RowPopover from "./popovers/Row.svelte"
import ColumnPopover from "./popovers/Column.svelte"
import ViewPopover from "./popovers/View.svelte"

View File

@ -9,7 +9,7 @@
import ActionButton from "components/common/ActionButton.svelte"
import LinkedRecord from "./LinkedRecord.svelte"
import TablePagination from "./TablePagination.svelte"
import { DeleteRecordModal, CreateEditRecordModal } from "./modals"
import { CreateEditRecordModal } from "./modals"
import RowPopover from "./popovers/Row.svelte"
import ColumnPopover from "./popovers/Column.svelte"
import ViewPopover from "./popovers/View.svelte"

View File

@ -52,7 +52,7 @@
}
</script>
<div class="actions">
<div class="container">
<Input placeholder="Name" thin bind:value={field.name} />
<Select
@ -65,76 +65,62 @@
{/each}
</Select>
<div class="info">
<div class="field">
<label>Required</label>
<input
type="checkbox"
bind:checked={required}
on:change={() => (field.constraints.presence.allowEmpty = required)} />
</div>
{#if field.type === 'string' && field.constraints}
<NumberBox
label="Max Length"
bind:value={field.constraints.length.maximum} />
<ValuesList label="Categories" bind:values={field.constraints.inclusion} />
{:else if field.type === 'datetime' && field.constraints}
<DatePicker
label="Earliest"
bind:value={field.constraints.datetime.earliest} />
<DatePicker label="Latest" bind:value={field.constraints.datetime.latest} />
{:else if field.type === 'number' && field.constraints}
<NumberBox
label="Min Value"
bind:value={field.constraints.numericality.greaterThanOrEqualTo} />
<NumberBox
label="Max Value"
bind:value={field.constraints.numericality.lessThanOrEqualTo} />
{:else if field.type === 'link'}
<div class="field">
<label>Required</label>
<input
type="checkbox"
bind:checked={required}
on:change={() => (field.constraints.presence.allowEmpty = required)} />
<label>Link</label>
<select class="budibase__input" bind:value={field.modelId}>
<option value="">Choose an option</option>
{#each $backendUiStore.models as model}
{#if model._id !== $backendUiStore.draftModel._id}
<option value={model._id}>{model.name}</option>
{/if}
{/each}
</select>
</div>
{#if field.type === 'string' && field.constraints}
<NumberBox
label="Max Length"
bind:value={field.constraints.length.maximum} />
<ValuesList
label="Categories"
bind:values={field.constraints.inclusion} />
{:else if field.type === 'datetime' && field.constraints}
<DatePicker
label="Earliest"
bind:value={field.constraints.datetime.earliest} />
<DatePicker
label="Latest"
bind:value={field.constraints.datetime.latest} />
{:else if field.type === 'number' && field.constraints}
<NumberBox
label="Min Value"
bind:value={field.constraints.numericality.greaterThanOrEqualTo} />
<NumberBox
label="Max Value"
bind:value={field.constraints.numericality.lessThanOrEqualTo} />
{:else if field.type === 'link'}
<div class="field">
<label>Link</label>
<select class="budibase__input" bind:value={field.modelId}>
<option value={''} />
{#each $backendUiStore.models as model}
{#if model._id !== $backendUiStore.draftModel._id}
<option value={model._id}>{model.name}</option>
{/if}
{/each}
</select>
</div>
{/if}
</div>
</div>
<footer>
<div class="button-margin-3">
{/if}
<footer>
<Button secondary on:click={onClosed}>Cancel</Button>
</div>
<div class="button-margin-4">
<Button primary on:click={saveColumn}>Save Column</Button>
</div>
</footer>
</footer>
</div>
<style>
.actions {
padding: var(--spacing-l) var(--spacing-xl);
.container {
padding: var(--spacing-xl);
display: grid;
grid-gap: var(--spacing-xl);
min-width: 400px;
}
footer {
padding: 20px 30px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 20px;
background: var(--grey-1);
border-bottom-left-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
display: flex;
justify-content: flex-end;
gap: var(--spacing-m);
}
.field {
@ -144,17 +130,6 @@
grid-gap: 5px;
font-size: 14px;
font-weight: 500;
margin-bottom: var(--spacing-l);
font-family: var(--font-normal);
}
.button-margin-3 {
grid-column-start: 3;
display: grid;
}
.button-margin-4 {
grid-column-start: 4;
display: grid;
}
</style>

View File

@ -40,11 +40,11 @@
}
</script>
<div class="actions">
<div class="container">
<ErrorsBox {errors} />
<form on:submit|preventDefault>
{#each modelSchema as [key, meta]}
<div class="bb-margin-xl">
<div>
{#if meta.type === 'link'}
<LinkedRecordSelector
bind:linked={record[key]}
@ -56,38 +56,28 @@
</div>
{/each}
</form>
</div>
<footer>
<div class="button-margin-3">
<footer>
<Button secondary on:click={onClosed}>Cancel</Button>
</div>
<div class="button-margin-4">
<Button primary on:click={saveRecord}>Save</Button>
</div>
</footer>
</footer>
</div>
<style>
.actions {
padding: var(--spacing-l) var(--spacing-xl);
.container {
padding: var(--spacing-xl);
display: grid;
grid-gap: var(--spacing-xl);
min-width: 400px;
}
form {
display: grid;
grid-gap: var(--spacing-xl);
}
footer {
padding: 20px 30px;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 20px;
background: var(--grey-1);
border-bottom-left-radius: 0.5rem;
border-bottom-left-radius: 0.5rem;
}
.button-margin-3 {
grid-column-start: 3;
display: grid;
}
.button-margin-4 {
grid-column-start: 4;
display: grid;
display: flex;
justify-content: flex-end;
gap: var(--spacing-m);
}
</style>

View File

@ -1,61 +0,0 @@
<script>
import ActionButton from "components/common/ActionButton.svelte"
import { notifier } from "builderStore/store/notifications"
import { store, backendUiStore } from "builderStore"
import * as api from "../api"
export let record
export let onClosed
</script>
<section>
<div class="content">
<header>
<i class="ri-information-line alert" />
<h4 class="budibase__title--4">Delete Record</h4>
</header>
<p>
Are you sure you want to delete this record? All of your data will be
permanently removed. This action cannot be undone.
</p>
</div>
<div class="modal-actions">
<ActionButton on:click={onClosed}>Cancel</ActionButton>
<ActionButton
alert
on:click={async () => {
await api.deleteRecord(record)
notifier.danger('Record deleted')
backendUiStore.actions.records.delete(record)
onClosed()
}}>
Delete
</ActionButton>
</div>
</section>
<style>
.alert {
color: rgba(255, 0, 31, 1);
background: var(--grey-1);
padding: 5px;
}
.modal-actions {
padding: 10px;
background: var(--grey-1);
border-top: 1px solid #ccc;
}
header {
display: flex;
align-items: center;
}
.content {
padding: 30px;
}
h4 {
margin: 0 0 0 10px;
}
</style>

View File

@ -41,7 +41,7 @@
{#if type === 'select'}
<Select thin secondary data-cy="{meta.name}-select" bind:value>
<option />
<option value="">Choose an option</option>
{#each meta.constraints.inclusion as opt}
<option value={opt}>{opt}</option>
{/each}
@ -52,24 +52,34 @@
{:else if type === 'file'}
<Label small forAttr={'dropzone-label'}>{meta.name}</Label>
<Dropzone bind:files={value} />
{:else if type === 'checkbox'}
<div class="checkbox">
<Label small forAttr={'checkbox-label'}>{meta.name}</Label>
<input
checked={value}
data-cy="{meta.name}-input"
{type}
on:change={handleInput} />
</div>
{:else}
{#if type === 'checkbox'}
<label>{meta.name}</label>
{/if}
<Input
thin
placeholder={meta.name}
data-cy="{meta.name}-input"
checked={value}
{type}
{value}
on:change={handleInput} />
{/if}
<style>
label {
font-weight: 500;
font-size: var(--font-size-s);
margin-bottom: 12px;
.checkbox {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.checkbox :global(label) {
margin-bottom: 0;
margin-right: var(--spacing-xs);
}
</style>

View File

@ -1,2 +1,3 @@
export { default as DeleteRecordModal } from "./DeleteRecord.svelte"
export { default as CreateEditRecordModal } from "./CreateEditRecord.svelte"
export { default as CreateEditColumnModal } from "./CreateEditColumn.svelte"
export { default as RecordFieldControlModal } from "./RecordFieldControl.svelte"

View File

@ -46,47 +46,52 @@
</TextButton>
</div>
<Popover bind:this={dropdown} {anchor} align="left">
<h5>Calculate</h5>
<div class="input-group-row">
<p>The</p>
<Select secondary thin bind:value={view.calculation}>
<option value={null} />
{#each CALCULATIONS as calculation}
<option value={calculation.key}>{calculation.name}</option>
{/each}
</Select>
<p>of</p>
<Select secondary thin bind:value={view.field}>
<option value={null} />
{#each fields as field}
<option value={field}>{field}</option>
{/each}
</Select>
</div>
<div class="button-group">
<Button secondary on:click={dropdown.hide}>Cancel</Button>
<Button primary on:click={saveView}>Save</Button>
<div class="container">
<h5>Calculate</h5>
<div class="input-group-row">
<p>The</p>
<Select secondary thin bind:value={view.calculation}>
<option value="">Choose an option</option>
{#each CALCULATIONS as calculation}
<option value={calculation.key}>{calculation.name}</option>
{/each}
</Select>
<p>of</p>
<Select secondary thin bind:value={view.field}>
<option value="">Choose an option</option>
{#each fields as field}
<option value={field}>{field}</option>
{/each}
</Select>
</div>
<div class="footer">
<Button secondary on:click={dropdown.hide}>Cancel</Button>
<Button primary on:click={saveView}>Save</Button>
</div>
</div>
</Popover>
<style>
.container {
display: grid;
grid-gap: var(--spacing-xl);
}
h5 {
margin-bottom: var(--spacing-l);
margin: 0;
font-weight: 500;
}
.button-group {
margin-top: var(--spacing-l);
.footer {
display: flex;
justify-content: flex-end;
gap: var(--spacing-s);
gap: var(--spacing-m);
}
.input-group-row {
display: grid;
grid-template-columns: 50px 1fr 20px 1fr;
gap: var(--spacing-s);
margin-bottom: var(--spacing-l);
align-items: center;
}

View File

@ -2,7 +2,7 @@
import { backendUiStore } from "builderStore"
import { DropdownMenu, Button, Icon, Input, Select } from "@budibase/bbui"
import { FIELDS } from "constants/backend"
import CreateEditColumn from "../modals/CreateEditColumn.svelte"
import { CreateEditColumnModal } from "../modals"
export let field
@ -37,14 +37,14 @@
}
</script>
<div bind:this={anchor} on:click={dropdown.show}>
<div class="container" bind:this={anchor} on:click={dropdown.show}>
{field.name}
<Icon name="arrowdown" />
</div>
<DropdownMenu bind:this={dropdown} {anchor} align="left">
{#if editing}
<h5>Edit Column</h5>
<CreateEditColumn onClosed={hideEditor} {field} />
<CreateEditColumnModal onClosed={hideEditor} {field} />
{:else}
<ul>
<li data-cy="edit-column-header" on:click={showEditor}>
@ -72,6 +72,14 @@
</DropdownMenu>
<style>
.container {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-xs);
}
h5 {
padding: var(--spacing-xl) 0 0 var(--spacing-xl);
margin: 0;

View File

@ -10,17 +10,17 @@
Heading,
} from "@budibase/bbui"
import { FIELDS } from "constants/backend"
import CreateEditRecord from "../modals/CreateEditRecord.svelte"
import DeleteRecordModal from "../modals/DeleteRecord.svelte"
const { open, close } = getContext("simple-modal")
import { CreateEditRecordModal } from "../modals"
import * as api from "../api"
import { notifier } from "builderStore/store/notifications"
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
export let row
let anchor
let dropdown
let editing
let confirmDeleteDialog
function showEditor() {
editing = true
@ -32,15 +32,11 @@
close()
}
const deleteRow = () => {
open(
DeleteRecordModal,
{
onClosed: hideEditor,
record: row,
},
{ styleContent: { padding: "0" } }
)
async function deleteRow() {
await api.deleteRecord(row)
notifier.success("Record deleted")
backendUiStore.actions.records.delete(row)
hideEditor()
}
</script>
@ -50,20 +46,26 @@
<DropdownMenu bind:this={dropdown} {anchor} align="left">
{#if editing}
<h5>Edit Row</h5>
<CreateEditRecord onClosed={hideEditor} record={row} />
<CreateEditRecordModal onClosed={hideEditor} record={row} />
{:else}
<ul>
<li data-cy="edit-row" on:click={showEditor}>
<Icon name="edit" />
<span>Edit</span>
</li>
<li data-cy="delete-row" on:click={deleteRow}>
<li data-cy="delete-row" on:click={() => confirmDeleteDialog.show()}>
<Icon name="delete" />
<span>Delete</span>
</li>
</ul>
{/if}
</DropdownMenu>
<ConfirmDialog
bind:this={confirmDeleteDialog}
body={`Are you sure you wish to delete this row? Your data will be deleted and this action cannot be undone.`}
okText="Delete Row"
onOk={deleteRow}
title="Confirm Delete" />
<style>
.ri-more-line:hover {
@ -91,6 +93,7 @@
margin: auto 0px;
align-items: center;
cursor: pointer;
font-size: var(--font-size-xs);
}
li:hover {

View File

@ -95,67 +95,80 @@
</TextButton>
</div>
<Popover bind:this={dropdown} {anchor} align="left">
<h5>Filter</h5>
<div class="input-group-row">
{#each view.filters as filter, idx}
{#if idx === 0}
<p>Where</p>
{:else}
<Select secondary thin bind:value={filter.conjunction}>
{#each CONJUNCTIONS as conjunction}
<option value={conjunction.key}>{conjunction.name}</option>
{/each}
</Select>
{/if}
<Select secondary thin bind:value={filter.key}>
{#each fields as field}
<option value={field}>{field}</option>
<div class="container">
<h5>Filter</h5>
{#if view.filters.length}
<div class="input-group-row">
{#each view.filters as filter, idx}
{#if idx === 0}
<p>Where</p>
{:else}
<Select secondary thin bind:value={filter.conjunction}>
<option value="">Choose an option</option>
{#each CONJUNCTIONS as conjunction}
<option value={conjunction.key}>{conjunction.name}</option>
{/each}
</Select>
{/if}
<Select secondary thin bind:value={filter.key}>
<option value="">Choose an option</option>
{#each fields as field}
<option value={field}>{field}</option>
{/each}
</Select>
<Select secondary thin bind:value={filter.condition}>
<option value="">Choose an option</option>
{#each CONDITIONS as condition}
<option value={condition.key}>{condition.name}</option>
{/each}
</Select>
{#if filter.key && isMultipleChoice(filter.key)}
<Select secondary thin bind:value={filter.value}>
<option value="">Choose an option</option>
{#each viewModel.schema[filter.key].constraints.inclusion as option}
<option value={option}>{option}</option>
{/each}
</Select>
{:else}
<Input
thin
placeholder={filter.key || fields[0]}
bind:value={filter.value} />
{/if}
<i class="ri-close-circle-fill" on:click={() => removeFilter(idx)} />
{/each}
</Select>
<Select secondary thin bind:value={filter.condition}>
{#each CONDITIONS as condition}
<option value={condition.key}>{condition.name}</option>
{/each}
</Select>
{#if filter.key && isMultipleChoice(filter.key)}
<Select secondary thin bind:value={filter.value}>
{#each viewModel.schema[filter.key].constraints.inclusion as option}
<option value={option}>{option}</option>
{/each}
</Select>
{:else}
<Input
thin
placeholder={filter.key || fields[0]}
bind:value={filter.value} />
{/if}
<i class="ri-close-circle-fill" on:click={() => removeFilter(idx)} />
{/each}
</div>
<div class="button-group">
<Button text on:click={addFilter}>Add Filter</Button>
<div>
<Button secondary on:click={dropdown.hide}>Cancel</Button>
<Button primary on:click={saveView}>Save</Button>
</div>
{/if}
<div class="footer">
<Button text on:click={addFilter}>Add Filter</Button>
<div class="buttons">
<Button secondary on:click={dropdown.hide}>Cancel</Button>
<Button primary on:click={saveView}>Save</Button>
</div>
</div>
</div>
</Popover>
<style>
.container {
display: grid;
grid-gap: var(--spacing-xl);
}
h5 {
margin-bottom: var(--spacing-l);
margin: 0;
font-weight: 500;
}
.button-group {
margin-top: var(--spacing-l);
.footer {
display: flex;
justify-content: space-between;
align-items: center;
}
:global(.button-group > div > button) {
margin-left: var(--spacing-m);
.buttons {
display: flex;
justify-content: flex-end;
gap: var(--spacing-m);
}
.ri-close-circle-fill {
@ -166,7 +179,6 @@
display: grid;
grid-template-columns: minmax(50px, auto) 1fr 1fr 1fr 15px;
gap: var(--spacing-s);
margin-bottom: var(--spacing-l);
align-items: center;
}

View File

@ -42,40 +42,45 @@
</TextButton>
</div>
<Popover bind:this={dropdown} {anchor} align="left">
<h5>Group By</h5>
<div class="input-group-row">
<p>Group By</p>
<Select secondary thin bind:value={view.groupBy}>
<option value={false} />
{#each fields as field}
<option value={field}>{field}</option>
{/each}
</Select>
</div>
<div class="button-group">
<Button secondary on:click={dropdown.hide}>Cancel</Button>
<Button primary on:click={saveView}>Save</Button>
<div class="container">
<h5>Group By</h5>
<div class="input-group-row">
<p>Group By</p>
<Select secondary thin bind:value={view.groupBy}>
<option value="">Choose an option</option>
{#each fields as field}
<option value={field}>{field}</option>
{/each}
</Select>
</div>
<div class="footer">
<Button secondary on:click={dropdown.hide}>Cancel</Button>
<Button primary on:click={saveView}>Save</Button>
</div>
</div>
</Popover>
<style>
.container {
display: grid;
grid-gap: var(--spacing-xl);
}
h5 {
margin-bottom: var(--spacing-l);
margin: 0;
font-weight: 500;
}
.button-group {
margin-top: var(--spacing-l);
.footer {
display: flex;
justify-content: flex-end;
gap: var(--spacing-s);
gap: var(--spacing-m);
}
.input-group-row {
display: grid;
grid-template-columns: 75px 1fr 20px 1fr;
grid-template-columns: 75px 1fr;
gap: var(--spacing-s);
margin-bottom: var(--spacing-l);
align-items: center;
}

View File

@ -48,32 +48,30 @@
</TextButton>
</div>
<Popover bind:this={dropdown} {anchor} align="left">
<h5>Create View</h5>
<div class="input-group-column">
<div class="container">
<h5>Create View</h5>
<Input placeholder="View Name" thin bind:value={name} />
</div>
<div class="button-group">
<Button secondary on:click={dropdown.hide}>Cancel</Button>
<Button primary on:click={saveView}>Save View</Button>
<div class="footer">
<Button secondary on:click={dropdown.hide}>Cancel</Button>
<Button primary on:click={saveView}>Save View</Button>
</div>
</div>
</Popover>
<style>
h5 {
margin-bottom: var(--spacing-l);
margin: 0;
font-weight: 500;
}
.button-group {
margin-top: var(--spacing-l);
display: flex;
justify-content: flex-end;
gap: var(--spacing-s);
.container {
display: grid;
grid-gap: var(--spacing-xl);
}
.input-group-column {
.footer {
display: flex;
flex-direction: column;
gap: var(--spacing-s);
justify-content: flex-end;
gap: var(--spacing-m);
}
</style>

View File

@ -38,15 +38,11 @@
placeholder="Table Name"
thin
bind:value={name} />
</div>
<footer>
<div class="button-margin-3">
<footer>
<Button secondary on:click={onClosed}>Cancel</Button>
</div>
<div class="button-margin-4">
<Button primary on:click={saveTable}>Save</Button>
</div>
</footer>
</footer>
</div>
</DropdownMenu>
<style>
@ -54,6 +50,7 @@
padding: var(--spacing-xl);
display: grid;
grid-gap: var(--spacing-xl);
min-width: 400px;
}
h5 {
@ -62,20 +59,8 @@
}
footer {
padding: var(--spacing-xl);
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
display: flex;
justify-content: flex-end;
gap: var(--spacing-m);
background: var(--grey-1);
}
.button-margin-3 {
grid-column-start: 3;
display: grid;
}
.button-margin-4 {
grid-column-start: 4;
display: grid;
}
</style>

View File

@ -43,15 +43,11 @@
<div class="container">
<h5>Edit Table</h5>
<Input placeholder="Table Name" thin bind:value={table.name} />
</div>
<footer>
<div class="button-margin-3">
<footer>
<Button secondary on:click={hideEditor}>Cancel</Button>
</div>
<div class="button-margin-4">
<Button primary on:click={save}>Save</Button>
</div>
</footer>
</footer>
</div>
{:else}
<ul>
<li on:click={showEditor}>
@ -88,6 +84,7 @@
padding: var(--spacing-xl);
display: grid;
grid-gap: var(--spacing-xl);
min-width: 400px;
}
h5 {
@ -96,11 +93,9 @@
}
footer {
padding: var(--spacing-xl);
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
display: flex;
justify-content: flex-end;
gap: var(--spacing-m);
background: var(--grey-1);
}
ul {
@ -127,19 +122,4 @@
li:active {
color: var(--blue);
}
.button-margin-1 {
grid-column-start: 1;
display: grid;
}
.button-margin-3 {
grid-column-start: 3;
display: grid;
}
.button-margin-4 {
grid-column-start: 4;
display: grid;
}
</style>

View File

@ -51,15 +51,11 @@
<div class="container">
<h5>Edit View</h5>
<Input placeholder="View Name" thin bind:value={view.name} />
</div>
<footer>
<div class="button-margin-3">
<footer>
<Button secondary on:click={hideEditor}>Cancel</Button>
</div>
<div class="button-margin-4">
<Button primary on:click={save}>Save</Button>
</div>
</footer>
</footer>
</div>
{:else}
<ul>
<li on:click={showEditor}>
@ -96,6 +92,7 @@
padding: var(--spacing-xl);
display: grid;
grid-gap: var(--spacing-xl);
min-width: 400px;
}
h5 {
@ -104,11 +101,9 @@
}
footer {
padding: var(--spacing-xl);
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
display: flex;
justify-content: flex-end;
gap: var(--spacing-m);
background: var(--grey-1);
}
ul {
@ -135,19 +130,4 @@
li:active {
color: var(--blue);
}
.button-margin-1 {
grid-column-start: 1;
display: grid;
}
.button-margin-3 {
grid-column-start: 3;
display: grid;
}
.button-margin-4 {
grid-column-start: 4;
display: grid;
}
</style>