1
0
Fork 0
mirror of synced 2024-08-27 07:51:37 +12:00

merge spectrum-bbui

This commit is contained in:
Keviin Åberg Kultalahti 2021-04-21 13:15:16 +02:00
commit a84b1ed316
162 changed files with 2479 additions and 9516 deletions

View file

@ -2,6 +2,7 @@
"name": "@budibase/bbui", "name": "@budibase/bbui",
"description": "A UI solution used in the different Budibase projects.", "description": "A UI solution used in the different Budibase projects.",
"version": "1.58.13", "version": "1.58.13",
"license": "AGPL-3.0",
"svelte": "src/index.js", "svelte": "src/index.js",
"module": "dist/bbui.es.js", "module": "dist/bbui.es.js",
"exports": { "exports": {
@ -9,31 +10,24 @@
"import": "./dist/bbui.es.js" "import": "./dist/bbui.es.js"
}, },
"./package.json": "./package.json", "./package.json": "./package.json",
"./dist/style.css": "./dist/style.css" "./spectrum-icons-rollup.js": "./src/spectrum-icons-rollup.js",
"./spectrum-icons-vite.js": "./src/spectrum-icons-vite.js"
}, },
"scripts": { "scripts": {
"dev:builder": "vite build", "build": "rollup -c"
"build": "vite build"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^16.0.0", "@rollup/plugin-commonjs": "^16.0.0",
"@rollup/plugin-json": "^4.1.0", "@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.0.0", "@rollup/plugin-node-resolve": "^11.2.1",
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.5",
"cross-env": "^7.0.2", "cross-env": "^7.0.2",
"nollup": "^0.14.1", "nollup": "^0.14.1",
"postcss": "^8.2.9", "postcss": "^8.2.9",
"rollup": "^2.34.0", "rollup": "^2.45.2",
"rollup-plugin-copy": "^3.3.0",
"rollup-plugin-delete": "^1.2.0",
"rollup-plugin-hot": "^0.1.1",
"rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-postcss": "^4.0.0", "rollup-plugin-postcss": "^4.0.0",
"rollup-plugin-svelte-hot": "^0.11.0", "rollup-plugin-svelte": "^7.1.0",
"semantic-release": "^17.0.8", "rollup-plugin-terser": "^7.0.2",
"svelte": "^3.37.0", "svelte": "^3.37.0"
"svench": "^0.0.10-7",
"vite": "^2.1.5"
}, },
"keywords": [ "keywords": [
"svelte" "svelte"
@ -43,34 +37,39 @@
"dist" "dist"
], ],
"dependencies": { "dependencies": {
"@adobe/spectrum-css-workflow-icons": "^1.2.1",
"@spectrum-css/actionbutton": "^1.0.1", "@spectrum-css/actionbutton": "^1.0.1",
"@spectrum-css/actiongroup": "^1.0.1", "@spectrum-css/actiongroup": "^1.0.1",
"@spectrum-css/button": "^3.0.1", "@spectrum-css/button": "^3.0.1",
"@spectrum-css/buttongroup": "^3.0.1", "@spectrum-css/buttongroup": "^3.0.1",
"@spectrum-css/checkbox": "^3.0.1", "@spectrum-css/checkbox": "^3.0.2",
"@spectrum-css/dialog": "^3.0.1", "@spectrum-css/dialog": "^3.0.1",
"@spectrum-css/divider": "^1.0.1", "@spectrum-css/divider": "^1.0.1",
"@spectrum-css/fieldgroup": "^3.0.2",
"@spectrum-css/fieldlabel": "^3.0.1",
"@spectrum-css/icon": "^3.0.1", "@spectrum-css/icon": "^3.0.1",
"@spectrum-css/inputgroup": "^3.0.2",
"@spectrum-css/label": "^2.0.9", "@spectrum-css/label": "^2.0.9",
"@spectrum-css/link": "^3.1.1", "@spectrum-css/link": "^3.1.1",
"@spectrum-css/menu": "^3.0.1", "@spectrum-css/menu": "^3.0.1",
"@spectrum-css/modal": "^3.0.1", "@spectrum-css/modal": "^3.0.1",
"@spectrum-css/picker": "^1.0.1",
"@spectrum-css/popover": "^3.0.1", "@spectrum-css/popover": "^3.0.1",
"@spectrum-css/progressbar": "^1.0.2", "@spectrum-css/progressbar": "^1.0.2",
"@spectrum-css/progresscircle": "^1.0.2", "@spectrum-css/progresscircle": "^1.0.2",
"@spectrum-css/radio": "^3.0.2",
"@spectrum-css/search": "^3.0.2",
"@spectrum-css/switch": "^1.0.2",
"@spectrum-css/table": "^3.0.1", "@spectrum-css/table": "^3.0.1",
"@spectrum-css/tabs": "^3.0.1", "@spectrum-css/tabs": "^3.0.1",
"@spectrum-css/textfield": "^3.0.1",
"@spectrum-css/toast": "^3.0.1", "@spectrum-css/toast": "^3.0.1",
"@spectrum-css/treeview": "^3.0.2", "@spectrum-css/treeview": "^3.0.2",
"@spectrum-css/typography": "^3.0.1", "@spectrum-css/typography": "^3.0.1",
"@spectrum-css/underlay": "^2.0.9", "@spectrum-css/underlay": "^2.0.9",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",
"dayjs": "^1.10.4", "dayjs": "^1.10.4",
"markdown-it": "^12.0.4", "svelte-flatpickr": "^3.1.0",
"quill": "^1.3.7", "svelte-portal": "^1.0.0"
"sirv-cli": "^0.4.6",
"svelte-flatpickr": "^2.4.0",
"svelte-portal": "^1.0.0",
"turndown": "^7.0.0"
} }
} }

View file

@ -1,140 +1,25 @@
import * as path from "path" import svelte from "rollup-plugin-svelte"
import svelte from "rollup-plugin-svelte-hot"
import resolve from "@rollup/plugin-node-resolve" import resolve from "@rollup/plugin-node-resolve"
import commonjs from "@rollup/plugin-commonjs" import commonjs from "@rollup/plugin-commonjs"
import json from "@rollup/plugin-json" import json from "@rollup/plugin-json"
import copy from "rollup-plugin-copy" import { terser } from "rollup-plugin-terser"
import hmr from "rollup-plugin-hot"
import del from "rollup-plugin-delete"
import postcss from "rollup-plugin-postcss" import postcss from "rollup-plugin-postcss"
import { plugin as Svench } from "svench/rollup"
import builtins from "rollup-plugin-node-builtins"
const WATCH = !!process.env.ROLLUP_WATCH export default {
const SVENCH = !!process.env.SVENCH input: "src/index.js",
const HOT = WATCH output: {
const PRODUCTION = !WATCH sourcemap: true,
format: "esm",
const svench = Svench({ file: "dist/bbui.es.js",
// The root dir that Svench will parse and watch.
//
// NOTE Watching the root of the project, to let Svench render *.md for us.
//
// NOTE By default, `node_modules` and `.git` dirs are ignored. This can be
// customized by passing a function to `ignore` option. Default ignore is:
//
// ignore: path => /(?:^|\/)(?:node_modules|\.git)\//.test(path),
//
dir: ".",
// Make `src` dir a section (that is, it will always be "expanded" in the
// menu).
autoSections: ["src"],
// Use custom index.html
index: {
source: "public/index.html",
}, },
plugins: [
extensions: [".svench", ".svench.svelte", ".svench.svx", ".md"], resolve(),
commonjs(),
serve: WATCH && { svelte({
host: "0.0.0.0", emitCss: true,
port: 4242, }),
public: "public", postcss(),
nollup: "0.0.0.0:42421", terser(),
}, json(),
}) ],
// NOTE configs are in function form to avoid instantiating plugins of the
// config that is not used for nothing (in particular, the HMR plugin launches
// a dev server on startup, this is not desired when just building for prod)
const configs = {
svench: () => ({
input: ".svench/svench.js",
output: {
format: "es",
dir: "public/svench",
},
plugins: [
builtins(),
// NOTE cleaning old builds is required to avoid serving stale static
// files from a previous build instead of in-memory files from the dev/hmr
// server
del({
targets: "public/svench/*",
runOnce: true,
}),
postcss({
hot: HOT,
extract: path.resolve("public/svench/theme.css"),
sourceMap: true,
}),
svench,
svelte({
dev: !PRODUCTION,
extensions: [".svelte", ".svench", ".svx", ".md"],
// Svench's "combined" preprocessor wraps both Mdsvex preprocessors
// (configured for Svench), and its own preprocessor (for static
// analysis -- eg extract source from views)
preprocess: svench.$.preprocess,
hot: HOT && {
optimistic: true,
noPreserveState: false,
},
}),
resolve({ browser: true }),
commonjs(),
json(),
HOT &&
hmr({
host: "0.0.0.0",
public: "public",
inMemory: true,
compatModuleHot: !HOT, // for terser
}),
],
watch: {
clearScreen: false,
// buildDelay is needed to ensure Svench's code (routes) generator will
// pick file changes before Rollup and prevent a double build (if Rollup
// first sees a change to src/Foo.svench, then to Svench's routes.js)
buildDelay: 100,
},
}),
lib: () => ({
input: "src/index.js",
output: [{ file: "dist/bundle.mjs", format: "es" }],
plugins: [
svelte({
dev: !PRODUCTION,
extensions: [".svelte"],
emitCss: true,
}),
postcss(),
copy({
targets: [
{
src: ".svench/svench.css",
dest: "public",
rename: "global.css",
},
],
}),
resolve(),
commonjs(),
json(),
],
}),
} }
export default configs[SVENCH ? "svench" : "lib"]()

View file

@ -5,14 +5,14 @@ export default function clickOutside(element, callbackFunction) {
} }
} }
document.body.addEventListener("click", onClick, true) document.body.addEventListener("mousedown", onClick, true)
return { return {
update(newCallbackFunction) { update(newCallbackFunction) {
callbackFunction = newCallbackFunction callbackFunction = newCallbackFunction
}, },
destroy() { destroy() {
document.body.removeEventListener("click", onClick, true) document.body.removeEventListener("mousedown", onClick, true)
}, },
} }
} }

View file

@ -2,19 +2,13 @@
import "@spectrum-css/button/dist/index-vars.css" import "@spectrum-css/button/dist/index-vars.css"
export let disabled = false export let disabled = false
export let size = "M"
/** @type {('S', 'M', 'L', 'XL')} Size of button */ export let cta, primary, secondary, warning, overBackground
export let size = "M";
// Types
export let cta, primary, secondary, warning, overBackground;
export let quiet = false export let quiet = false
export let icon = undefined
export let icon = undefined; export let active = false
</script> </script>
<button <button
class:spectrum-Button--cta={cta} class:spectrum-Button--cta={cta}
class:spectrum-Button--primary={primary} class:spectrum-Button--primary={primary}
@ -22,11 +16,16 @@
class:spectrum-Button--warning={warning} class:spectrum-Button--warning={warning}
class:spectrum-Button--overBackground={overBackground} class:spectrum-Button--overBackground={overBackground}
class:spectrum-Button--quiet={quiet} class:spectrum-Button--quiet={quiet}
class:active
class="spectrum-Button spectrum-Button--size{size.toUpperCase()}" class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
{disabled} {disabled}
on:click|preventDefault> on:click|preventDefault>
{#if icon} {#if icon}
<svg class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}" focusable="false" aria-hidden="true" aria-label="{icon}"> <svg
class="spectrum-Icon spectrum-Icon--size{size.toUpperCase()}"
focusable="false"
aria-hidden="true"
aria-label={icon}>
<use xlink:href="#spectrum-icon-18-{icon}" /> <use xlink:href="#spectrum-icon-18-{icon}" />
</svg> </svg>
{/if} {/if}
@ -36,5 +35,13 @@
</button> </button>
<style> <style>
.spectrum-Button-label {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
button.active {
color: var(--spectrum-semantic-cta-color-background-default);
}
</style> </style>

View file

@ -1,45 +0,0 @@
<script>
import Flatpickr from "svelte-flatpickr"
import { Label } from "../"
import "flatpickr/dist/flatpickr.css"
const PICKER_OPTIONS = {
enableTime: true,
}
export let label
export let placeholder
export let value
export let thin = false
</script>
<div class:thin>
{#if label}
<Label extraSmall grey>{label}</Label>
{/if}
<Flatpickr {placeholder} options={PICKER_OPTIONS} on:change bind:value />
</div>
<style>
:global(.flatpickr-input) {
width: 100%;
min-width: 0;
box-sizing: border-box;
color: var(--ink);
border-radius: 5px;
border: none;
background-color: var(--grey-2);
padding: var(--spacing-m);
font-size: var(--font-size-s);
margin: 0;
outline: none;
border: var(--border-transparent);
}
:global(.flatpickr-input:focus) {
border: var(--border-blue);
}
div.thin :global(.flatpickr-input) {
font-size: var(--font-size-xs);
}
</style>

View file

@ -1,17 +0,0 @@
<script>
import { View } from "svench";
import DatePicker from "./DatePicker.svelte";
function handleChange(event) {
const [fullDate, shortDate, instance] = event.detail
alert("Date is " + fullDate)
}
</script>
<View name="default">
<DatePicker on:change={handleChange} label="Start Date" placeholder="Pick a date" />
</View>
<View name="thin">
<DatePicker on:change={handleChange} label="Start Date" thin placeholder="Pick a date" />
</View>

View file

@ -1,14 +1,16 @@
<script> <script>
import "@spectrum-css/divider/dist/index-vars.css" import "@spectrum-css/divider/dist/index-vars.css"
export let l = false export let l = false
export let m = false export let m = false
export let s = false export let s = false
export let vertical = false export let vertical = false
$: useDefault = ![l, m, s].includes(true)
</script> </script>
<hr <hr
class:spectrum-Divider--sizeL={l} class:spectrum-Divider--sizeL={l}
class:spectrum-Divider--sizeM={m} class:spectrum-Divider--sizeM={m || useDefault}
class:spectrum-Divider--sizeS={s} class:spectrum-Divider--sizeS={s}
class="spectrum-Divider spectrum-Divider--{vertical ? 'vertical' : 'horizontal'} spectrum-Dialog-divider"> class="spectrum-Divider spectrum-Divider--{vertical ? 'vertical' : 'horizontal'} spectrum-Dialog-divider" />

View file

@ -1,7 +1,7 @@
<script> <script>
import { slide } from "svelte/transition" import { slide } from "svelte/transition"
import Portal from "svelte-portal" import Portal from "svelte-portal"
import ActionButton from '../ActionButton/ActionButton.svelte' import ActionButton from "../ActionButton/ActionButton.svelte"
export let title export let title
@ -84,5 +84,8 @@
} }
.text { .text {
display: flex; display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
} }
</style> </style>

View file

@ -2,7 +2,6 @@
import "@spectrum-css/popover/dist/index-vars.css" import "@spectrum-css/popover/dist/index-vars.css"
import Portal from "svelte-portal" import Portal from "svelte-portal"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import buildStyle from "../utils/buildStyle"
import positionDropdown from "../Actions/position_dropdown" import positionDropdown from "../Actions/position_dropdown"
import clickOutside from "../Actions/click_outside" import clickOutside from "../Actions/click_outside"
@ -10,7 +9,6 @@
export let anchor export let anchor
export let align = "right" export let align = "right"
export let borderColor = ""
export const show = () => { export const show = () => {
dispatch("open") dispatch("open")
@ -29,10 +27,6 @@
hide() hide()
} }
} }
$: menuStyle = buildStyle({
borderColor,
})
</script> </script>
{#if open} {#if open}
@ -41,9 +35,9 @@
tabindex="0" tabindex="0"
use:positionDropdown={{ anchor, align }} use:positionDropdown={{ anchor, align }}
use:clickOutside={hide} use:clickOutside={hide}
style={menuStyle}
on:keydown={handleEscape} on:keydown={handleEscape}
class="spectrum-Popover is-open" role="presentation"> class="spectrum-Popover is-open"
role="presentation">
<slot /> <slot />
</div> </div>
</Portal> </Portal>

View file

@ -1,140 +1,22 @@
<script> <script>
import Field from "./Field.svelte"
import Checkbox from "./Core/Checkbox.svelte"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
export let value = null
export let label = null
export let labelPosition = "above"
export let text = null
export let disabled = false
export let error = null
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => {
export let checked = false value = e.detail
export let value dispatch("change", e.detail)
export let name
export let disabled
function handleChange() {
if (disabled) return
checked = !checked
dispatch("change", checked)
} }
</script> </script>
<div class="container"> <Field {label} {labelPosition} {disabled} {error}>
<input <Checkbox {error} {disabled} {text} {value} on:change={onChange} />
{disabled} </Field>
on:change={handleChange}
{value}
bind:checked
type="checkbox"
{name}
class="checkbox"
id={value} />
<div class="checkbox-container" on:click={handleChange}>
<div class:disabled class="check-div" class:checked>
<div class="tick_mark" />
</div>
</div>
<slot />
</div>
<style>
.container {
display: flex;
gap: var(--spacing-s);
}
.checkbox-container {
position: relative;
z-index: 0;
}
.checkbox {
display: none;
}
.check-div {
position: relative;
width: 20px;
height: 20px;
background-color: var(--grey-2);
cursor: pointer;
transition: 0.2s ease transform, 0.2s ease background-color,
0.2s ease box-shadow;
overflow: hidden;
border-radius: 4px;
}
.check-div:before {
content: "";
position: absolute;
top: 50%;
right: 0;
left: 0;
width: 12px;
height: 12px;
margin: 0 auto;
background-color: var(--background);
transform: translateY(-50%);
transition: 0.2s ease width, 0.2s ease height;
border-radius: 2px;
}
.check-div:active {
transform: translateY(-50%) scale(0.9);
}
.tick_mark {
position: absolute;
top: 50%;
left: 6px;
width: 5px;
height: 4px;
margin: 0 auto;
transform: rotateZ(-40deg);
}
.tick_mark:before,
.tick_mark:after {
content: "";
position: absolute;
background-color: var(--ink);
border-radius: 2px;
opacity: 0;
transition: 0.2s ease transform, 0.2s ease opacity;
}
.tick_mark:before {
left: 0;
bottom: 0;
width: 2px;
height: 6px;
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.23);
transform: translateY(-68px);
}
.tick_mark:after {
left: 0;
bottom: 0;
width: 12px;
height: 2px;
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.23);
transform: translateX(78px);
}
.check-div.disabled:active {
transform: none;
}
.checked {
background-color: var(--grey-2);
}
.checked.disabled {
background-color: var(--grey-5);
}
.checked:before {
width: 0;
height: 0;
}
.checked .tick_mark:before,
.checked .tick_mark:after {
transform: translate(0);
opacity: 1;
}
</style>

View file

@ -1,67 +0,0 @@
<script>
import { View } from "svench";
import Checkbox from "./Checkbox.svelte";
let checked = false
let menu = [
{text: 'Cookies and cream', checked: false},
{text: 'Mint choc chip', checked: false},
{text: 'Raspberry ripple', checked: true}
];
</script>
<View name="Single checkbox">
<Checkbox bind:checked value="value">
<label for="value">One single checkbox with text</label>
</Checkbox>
</View>
<View name="Single disabled checkbox">
<Checkbox disabled checked value="value">
<label for="someOtherValue">A disabled checkbox</label>
</Checkbox>
</View>
<View name="No text">
<Checkbox bind:checked value="somevalue" />
</View>
## Multiple checkboxes
Use an array and an each block to use multiple checkboxes
```svelte
<script>
let menu = [
{text: 'Cookies and cream', checked: false},
{text: 'Mint choc chip', checked: false},
{text: 'Raspberry ripple', checked: true}
];
</script>
{#each menu as {text, checked}}
<Checkbox value={text} bind:checked>
<label for={text}>{text}</label>
</Checkbox>
{/each}
```
<View name="Multiple checkboxes">
<div class="container">
{#each menu as {text, checked}}
<Checkbox value={text} bind:checked>
<label for={text}>{text}</label>
</Checkbox>
{/each}
</div>
</View>
<style>
label {
display: grid;
place-items: center;
}
.container {
display: grid;
grid-gap: 10px;
}
</style>

View file

@ -0,0 +1,39 @@
<script>
import Field from "./Field.svelte"
import Combobox from "./Core/Combobox.svelte"
import { createEventDispatcher } from "svelte"
export let value = null
export let label = undefined
export let disabled = false
export let labelPosition = "above"
export let error = null
export let placeholder = "Choose an option"
export let options = []
export let getOptionLabel = option => extractProperty(option, "label")
export let getOptionValue = option => extractProperty(option, "value")
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
const extractProperty = (value, property) => {
if (value && typeof value === "object") {
return value[property]
}
return value
}
</script>
<Field {label} {labelPosition} {disabled} {error}>
<Combobox
{error}
{disabled}
{value}
{options}
{placeholder}
{getOptionLabel}
{getOptionValue}
on:change={onChange} />
</Field>

View file

@ -0,0 +1,43 @@
<script>
import "@spectrum-css/checkbox/dist/index-vars.css"
import "@spectrum-css/fieldgroup/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
export let value = false
export let error = null
export let id = null
export let text = null
export let disabled = false
const dispatch = createEventDispatcher()
const onChange = event => {
dispatch("change", event.target.checked)
}
</script>
<label
class="spectrum-Checkbox spectrum-Checkbox--sizeM spectrum-Checkbox--emphasized"
class:is-invalid={!!error}>
<input
checked={value}
{disabled}
on:change={onChange}
type="checkbox"
class="spectrum-Checkbox-input"
{id} />
<span class="spectrum-Checkbox-box">
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Checkbox-checkmark"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
<svg
class="spectrum-Icon spectrum-UIIcon-Dash100 spectrum-Checkbox-partialCheckmark"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Dash100" />
</svg>
</span>
<span class="spectrum-Checkbox-label">{text || ''}</span>
</label>

View file

@ -0,0 +1,128 @@
<script>
import "@spectrum-css/inputgroup/dist/index-vars.css"
import "@spectrum-css/popover/dist/index-vars.css"
import "@spectrum-css/menu/dist/index-vars.css"
import { fly } from "svelte/transition"
import { createEventDispatcher } from "svelte"
export let value = null
export let id = null
export let placeholder = "Choose an option"
export let disabled = false
export let error = null
export let options = []
export let getOptionLabel = option => option
export let getOptionValue = option => option
const dispatch = createEventDispatcher()
let open = false
let focus = false
$: fieldText = getFieldText(value, options, placeholder)
const getFieldText = (value, options, placeholder) => {
// Always use placeholder if no value
if (value == null || value === "") {
return placeholder || "Choose an option"
}
// Wait for options to load if there is a value but no options
if (!options?.length) {
return ""
}
// Render the label if the selected option is found, otherwise raw value
const selected = options.find(option => getOptionValue(option) === value)
return selected ? getOptionLabel(selected) : value
}
const selectOption = value => {
dispatch("change", value)
open = false
}
const onChange = e => {
selectOption(e.target.value)
}
</script>
<div class="spectrum-InputGroup" class:is-focused={open || focus}>
<div
class="spectrum-Textfield spectrum-InputGroup-textfield"
class:is-disabled={!!error}
class:is-focused={open || focus}>
<input
type="text"
on:focus={() => (focus = true)}
on:blur={() => (focus = false)}
on:change={onChange}
{value}
{placeholder}
class="spectrum-Textfield-input spectrum-InputGroup-input" />
</div>
<button
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
tabindex="-1"
aria-haspopup="true"
disabled={!!error}
on:click={() => (open = true)}>
<svg
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon spectrum-InputGroup-icon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Chevron100" />
</svg>
</button>
{#if open}
<div class="overlay" on:mousedown|self={() => (open = false)} />
<div
transition:fly={{ y: -20, duration: 200 }}
class="spectrum-Popover spectrum-Popover--bottom is-open">
<ul class="spectrum-Menu" role="listbox">
{#if options && Array.isArray(options)}
{#each options as option}
<li
class="spectrum-Menu-item"
class:is-selected={getOptionValue(option) === value}
role="option"
aria-selected="true"
tabindex="0"
on:click={() => selectOption(getOptionValue(option))}>
<span
class="spectrum-Menu-itemLabel">{getOptionLabel(option)}</span>
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
</li>
{/each}
{/if}
</ul>
</div>
{/if}
</div>
<style>
.spectrum-InputGroup {
min-width: 0;
width: 100%;
}
.spectrum-Textfield-input {
width: 0;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 999;
}
.spectrum-Popover {
max-height: 240px;
width: 100%;
z-index: 999;
top: 100%;
}
</style>

View file

@ -0,0 +1,142 @@
<script>
import Flatpickr from "svelte-flatpickr"
import "flatpickr/dist/flatpickr.css"
import "@spectrum-css/inputgroup/dist/index-vars.css"
import "@spectrum-css/textfield/dist/index-vars.css"
import "@spectrum-css/picker/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
import { generateID } from "../../utils/helpers"
export let id = null
export let disabled = false
export let error = null
export let isPlaceholder = false
export let enableTime = true
export let value = null
export let placeholder = null
const dispatch = createEventDispatcher()
const flatpickrId = `${generateID()}-wrapper`
let open = false
let flatpickr
$: flatpickrOptions = {
element: `#${flatpickrId}`,
enableTime: enableTime || false,
altInput: true,
altFormat: enableTime ? "F j Y, H:i" : "F j, Y",
wrap: true,
}
const handleChange = event => {
const [dates] = event.detail
dispatch("change", dates[0])
}
const clearDateOnBackspace = event => {
if (["Backspace", "Clear", "Delete"].includes(event.key)) {
dispatch("change", null)
flatpickr.close()
}
}
const onOpen = () => {
open = true
document.addEventListener("keyup", clearDateOnBackspace)
}
const onClose = () => {
open = false
document.removeEventListener("keyup", clearDateOnBackspace)
// Manually blur all input fields since flatpickr creates a second
// duplicate input field.
// We need to blur both because the focus styling does not get properly
// applied.
const els = document.querySelectorAll(`#${flatpickrId} input`)
els.forEach(el => el.blur())
}
</script>
<Flatpickr
bind:flatpickr
{value}
on:open={onOpen}
on:close={onClose}
options={flatpickrOptions}
on:change={handleChange}
element={`#${flatpickrId}`}>
<div
id={flatpickrId}
class:is-disabled={disabled}
class:is-invalid={!!error}
class="flatpickr spectrum-InputGroup spectrum-Datepicker"
class:is-focused={open}
aria-readonly="false"
aria-required="false"
aria-haspopup="true">
<div
on:click={flatpickr?.open}
class="spectrum-Textfield spectrum-InputGroup-textfield"
class:is-disabled={disabled}
class:is-invalid={!!error}>
{#if !!error}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<input
data-input
type="text"
{disabled}
class="spectrum-Textfield-input spectrum-InputGroup-input"
{placeholder}
{id}
{value} />
</div>
<button
type="button"
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
tabindex="-1"
{disabled}
class:is-invalid={!!error}
on:click={flatpickr?.open}>
<svg
class="spectrum-Icon spectrum-Icon--sizeM"
focusable="false"
aria-hidden="true"
aria-label="Calendar">
<use xlink:href="#spectrum-icon-18-Calendar" />
</svg>
</button>
</div>
</Flatpickr>
{#if open}
<div class="overlay" on:mousedown|self={flatpickr?.close} />
{/if}
<style>
.spectrum-Textfield-input {
pointer-events: none;
}
.spectrum-Textfield:not(.is-disabled):hover {
cursor: pointer;
}
.flatpickr {
width: 100%;
overflow: hidden;
}
.flatpickr .spectrum-Textfield {
width: 100%;
}
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
z-index: 999;
}
</style>

View file

@ -0,0 +1,81 @@
<script>
import Picker from "./Picker.svelte"
import { createEventDispatcher } from "svelte"
export let value = []
export let id = null
export let placeholder = null
export let disabled = false
export let error = null
export let options = []
export let getOptionLabel = option => option
export let getOptionValue = option => option
const dispatch = createEventDispatcher()
$: selectedLookupMap = getSelectedLookupMap(value)
$: optionLookupMap = getOptionLookupMap(options)
$: fieldText = getFieldText(value, optionLookupMap, placeholder)
$: isOptionSelected = optionValue => selectedLookupMap[optionValue] === true
$: toggleOption = makeToggleOption(selectedLookupMap, value)
const getFieldText = (value, map, placeholder) => {
if (value?.length) {
if (!map) {
return ""
}
const vals = value.map(option => map[option] || option).join(", ")
return `(${value.length}) ${vals}`
} else {
return placeholder || "Choose some options"
}
}
const getSelectedLookupMap = value => {
let map = {}
if (value?.length) {
value.forEach(option => {
if (option) {
map[option] = true
}
})
}
return map
}
const getOptionLookupMap = options => {
let map = null
if (options?.length) {
map = {}
options.forEach((option, idx) => {
const optionValue = getOptionValue(option, idx)
if (optionValue != null) {
map[optionValue] = getOptionLabel(option, idx) || ""
}
})
}
return map
}
const makeToggleOption = (map, value) => {
return optionValue => {
if (map[optionValue]) {
const filtered = value.filter(option => option !== optionValue)
dispatch("change", filtered)
} else {
dispatch("change", [...value, optionValue])
}
}
}
</script>
<Picker
{id}
{error}
{disabled}
{fieldText}
{options}
isPlaceholder={!value?.length}
{isOptionSelected}
{getOptionLabel}
{getOptionValue}
onSelectOption={toggleOption} />

View file

@ -0,0 +1,123 @@
<script>
import "@spectrum-css/picker/dist/index-vars.css"
import "@spectrum-css/popover/dist/index-vars.css"
import "@spectrum-css/menu/dist/index-vars.css"
import { fly } from "svelte/transition"
import { createEventDispatcher } from "svelte"
import clickOutside from "../../Actions/click_outside"
export let id = null
export let disabled = false
export let error = null
export let fieldText = ""
export let isPlaceholder = false
export let placeholderOption = null
export let options = []
export let isOptionSelected = () => false
export let onSelectOption = () => {}
export let getOptionLabel = option => option
export let getOptionValue = option => option
export let open = false
export let readonly = false
const dispatch = createEventDispatcher()
const onClick = e => {
dispatch("click")
if (readonly) {
return
}
open = true
}
</script>
<button
{id}
class="spectrum-Picker spectrum-Picker--sizeM"
{disabled}
class:is-invalid={!!error}
class:is-open={open}
aria-haspopup="listbox"
on:mousedown={onClick}>
<span class="spectrum-Picker-label" class:is-placeholder={isPlaceholder}>
{fieldText}
</span>
{#if error}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
focusable="false"
aria-hidden="true"
aria-label="Folder">
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<svg
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Chevron100" />
</svg>
</button>
{#if open}
<div
use:clickOutside={() => (open = false)}
transition:fly={{ y: -20, duration: 200 }}
class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open">
<ul class="spectrum-Menu" role="listbox">
{#if placeholderOption}
<li
class="spectrum-Menu-item"
class:is-selected={isPlaceholder}
role="option"
aria-selected="true"
tabindex="0"
on:click={() => onSelectOption(null)}>
<span class="spectrum-Menu-itemLabel">{placeholderOption}</span>
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
</li>
{/if}
{#if options && Array.isArray(options)}
{#each options as option, idx}
<li
class="spectrum-Menu-item"
class:is-selected={isOptionSelected(getOptionValue(option, idx))}
role="option"
aria-selected="true"
tabindex="0"
on:click={() => onSelectOption(getOptionValue(option, idx))}>
<span
class="spectrum-Menu-itemLabel">{getOptionLabel(option, idx)}</span>
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
</li>
{/each}
{/if}
</ul>
</div>
{/if}
<style>
.spectrum-Popover {
max-height: 240px;
width: 100%;
z-index: 999;
top: 100%;
}
.spectrum-Picker {
width: 100%;
}
.spectrum-Picker-label {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 0;
}
</style>

View file

@ -0,0 +1,36 @@
<script>
import "@spectrum-css/fieldgroup/dist/index-vars.css"
import "@spectrum-css/radio/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
export let direction = "vertical"
export let value = null
export let options = []
export let error = null
export let disabled = false
export let getOptionLabel = option => option
export let getOptionValue = option => option
const dispatch = createEventDispatcher()
const onChange = e => dispatch("change", e.target.value)
</script>
<div class={`spectrum-FieldGroup spectrum-FieldGroup--${direction}`}>
{#if options && Array.isArray(options)}
{#each options as option}
<div
class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized"
class:is-invalid={!!error}>
<input
on:change={onChange}
bind:group={value}
value={getOptionValue(option)}
type="radio"
class="spectrum-Radio-input"
{disabled} />
<span class="spectrum-Radio-button" />
<label class="spectrum-Radio-label">{getOptionLabel(option)}</label>
</div>
{/each}
{/if}
</div>

View file

@ -0,0 +1,82 @@
<script>
import "@spectrum-css/search/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
export let value = ""
export let placeholder = null
export let disabled = false
export let id = null
const dispatch = createEventDispatcher()
let focus = false
const updateValue = value => {
dispatch("change", value)
}
const onFocus = () => {
focus = true
}
const onBlur = event => {
focus = false
updateValue(event.target.value)
}
const updateValueOnEnter = event => {
if (event.key === "Enter") {
updateValue(event.target.value)
}
}
</script>
<div class="spectrum-Search" class:is-disabled={disabled}>
<div
class="spectrum-Textfield"
class:is-focused={focus}
class:is-disabled={disabled}>
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-icon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-icon-18-Magnify" />
</svg>
<input
on:click
on:keyup={updateValueOnEnter}
{disabled}
{id}
value={value || ''}
placeholder={placeholder || ''}
on:blur={onBlur}
on:focus={onFocus}
on:input
type="search"
class="spectrum-Textfield-input spectrum-Search-input"
autocomplete="off" />
</div>
<button
on:click={() => updateValue('')}
type="reset"
class="spectrum-ClearButton spectrum-Search-clearButton">
<svg
class="spectrum-Icon spectrum-UIIcon-Cross75"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Cross75" />
</svg>
</button>
</div>
<style>
.spectrum-Search,
.spectrum-Textfield {
width: 100%;
}
.spectrum-Search-input {
padding-right: 24px;
}
.is-disabled {
pointer-events: none;
}
</style>

View file

@ -0,0 +1,57 @@
<script>
import { createEventDispatcher } from "svelte"
import Picker from "./Picker.svelte"
export let value = null
export let id = null
export let placeholder = "Choose an option"
export let disabled = false
export let error = null
export let options = []
export let getOptionLabel = option => option
export let getOptionValue = option => option
export let readonly = false
const dispatch = createEventDispatcher()
let open = false
$: fieldText = getFieldText(value, options, placeholder)
const getFieldText = (value, options, placeholder) => {
// Always use placeholder if no value
if (value == null || value === "") {
return placeholder || "Choose an option"
}
// Wait for options to load if there is a value but no options
if (!options?.length) {
return ""
}
// Render the label if the selected option is found, otherwide raw value
const index = options.findIndex(
(option, idx) => getOptionValue(option, idx) === value
)
return index !== -1 ? getOptionLabel(options[index], index) : value
}
const selectOption = value => {
dispatch("change", value)
open = false
}
</script>
<Picker
on:click
bind:open
{id}
{error}
{disabled}
{readonly}
{fieldText}
{options}
{getOptionLabel}
{getOptionValue}
isPlaceholder={value == null || value === ''}
placeholderOption={placeholder}
isOptionSelected={option => option === value}
onSelectOption={selectOption} />

View file

@ -0,0 +1,27 @@
<script>
import "@spectrum-css/switch/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
export let value = false
export let error = null
export let id = null
export let text = null
export let disabled = false
const dispatch = createEventDispatcher()
const onChange = event => {
dispatch("change", event.target.checked)
}
</script>
<div class="spectrum-Switch spectrum-Switch--emphasized">
<input
checked={value}
{disabled}
on:change={onChange}
{id}
type="checkbox"
class="spectrum-Switch-input" />
<span class="spectrum-Switch-switch" />
<label class="spectrum-Switch-label" for={id}>{text}</label>
</div>

View file

@ -0,0 +1,55 @@
<script>
import "@spectrum-css/textfield/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
export let value = ""
export let placeholder = null
export let disabled = false
export let error = null
export let id = null
export const getCaretPosition = () => ({
start: textarea.selectionStart,
end: textarea.selectionEnd,
})
let focus = false
let textarea
const dispatch = createEventDispatcher()
const onChange = event => {
dispatch("change", event.target.value)
focus = false
}
</script>
<div
class="spectrum-Textfield spectrum-Textfield--multiline"
class:is-invalid={!!error}
class:is-disabled={disabled}
class:is-focused={focus}>
{#if error}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<textarea
bind:this={textarea}
placeholder={placeholder || ''}
class="spectrum-Textfield-input"
{disabled}
{id}
on:focus={() => (focus = true)}
on:blur={onChange}>{value || ''}</textarea>
</div>
<style>
.spectrum-Textfield {
width: 100%;
}
textarea {
resize: vertical;
min-height: 80px !important;
}
</style>

View file

@ -0,0 +1,84 @@
<script>
import "@spectrum-css/textfield/dist/index-vars.css"
import { createEventDispatcher } from "svelte"
export let value = ""
export let placeholder = null
export let type = "text"
export let disabled = false
export let error = null
export let id = null
export let readonly = false
const dispatch = createEventDispatcher()
let focus = false
const updateValue = value => {
if (readonly) {
return
}
if (type === "number") {
const float = parseFloat(value)
value = isNaN(float) ? null : float
}
dispatch("change", value)
}
const onFocus = () => {
if (readonly) {
return
}
focus = true
}
const onBlur = event => {
if (readonly) {
return
}
focus = false
updateValue(event.target.value)
}
const updateValueOnEnter = event => {
if (readonly) {
return
}
if (event.key === "Enter") {
updateValue(event.target.value)
}
}
</script>
<div
class="spectrum-Textfield"
class:is-invalid={!!error}
class:is-disabled={disabled}
class:is-focused={focus}>
{#if error}
<svg
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-icon-18-Alert" />
</svg>
{/if}
<input
on:click
on:keyup={updateValueOnEnter}
{disabled}
{readonly}
{id}
value={value || ''}
placeholder={placeholder || ''}
on:blur={onBlur}
on:focus={onFocus}
on:input
{type}
class="spectrum-Textfield-input" />
</div>
<style>
.spectrum-Textfield {
width: 100%;
}
</style>

View file

@ -0,0 +1,10 @@
export { default as CoreTextField } from "./TextField.svelte"
export { default as CoreSelect } from "./Select.svelte"
export { default as CoreMultiselect } from "./Multiselect.svelte"
export { default as CoreCheckbox } from "./Checkbox.svelte"
export { default as CoreRadioGroup } from "./RadioGroup.svelte"
export { default as CoreTextArea } from "./TextArea.svelte"
export { default as CoreCombobox } from "./Combobox.svelte"
export { default as CoreSwitch } from "./Switch.svelte"
export { default as CoreSearch } from "./Search.svelte"
export { default as CoreDatePicker } from "./DatePicker.svelte"

View file

@ -1,158 +0,0 @@
<script>
import Icon from "../Icons/Icon.svelte"
import Label from "../Styleguide/Label.svelte"
import { createEventDispatcher } from "svelte"
export let label = undefined
export let value = ""
export let name = undefined
export let thin = false
export let extraThin = false
export let secondary = false
export let outline = false
export let disabled = false
const dispatch = createEventDispatcher()
let focus = false
const updateValue = e => {
value = e.target.value
}
function handleFocus(e) {
focus = true
dispatch("focus", e)
}
function handleBlur(e) {
focus = false
dispatch("blur", e)
}
</script>
{#if label}
<Label extraSmall grey forAttr={name}>{label}</Label>
{/if}
<div class="container" class:disabled class:secondary class:outline class:focus>
<select
{name}
class:thin
class:extraThin
class:secondary
{disabled}
on:change
on:focus={handleFocus}
on:blur={handleBlur}
bind:value>
<slot />
</select>
<slot name="custom-input" />
<input
class:thin
class:extraThin
class:secondary
class:disabled
{disabled}
on:change={updateValue}
on:input={updateValue}
on:focus={handleFocus}
on:blur={e => {
updateValue(e)
handleBlur(e)
}}
value={value || ''}
type="text" />
<div class="pointer editable-pointer">
<Icon name="arrowdown" />
</div>
</div>
<style>
.container {
position: relative !important;
display: block;
border-radius: var(--border-radius-s);
border: var(--border-transparent);
background-color: var(--background);
}
.container.outline {
border: var(--border-dark);
}
.container.focus {
border: var(--border-blue);
}
input,
select {
border-radius: var(--border-radius-s);
font-size: var(--font-size-m);
outline: none;
border: none;
color: var(--ink);
text-align: left;
background-color: transparent;
}
select {
display: block !important;
width: 100% !important;
padding: var(--spacing-m) 2rem var(--spacing-m) var(--spacing-m);
appearance: none !important;
-webkit-appearance: none !important;
-moz-appearance: none !important;
align-items: center;
white-space: pre;
opacity: 0;
}
input {
position: absolute;
top: 0;
left: 0;
width: calc(100% - 30px);
height: 100%;
border: none;
box-sizing: border-box;
padding: var(--spacing-m) 0 var(--spacing-m) var(--spacing-m);
}
select.thin,
input.thin {
font-size: var(--font-size-xs);
}
select.extraThin,
input.extraThin {
font-size: var(--font-size-xs);
padding: var(--spacing-s) 0 var(--spacing-s) var(--spacing-m);
}
.secondary {
background: var(--grey-2);
}
select:disabled,
input:disabled,
.disabled {
background: var(--grey-4);
color: var(--grey-6);
}
.pointer {
right: 0 !important;
top: 0 !important;
bottom: 0 !important;
position: absolute !important;
pointer-events: none !important;
align-items: center !important;
display: flex !important;
box-sizing: border-box;
}
.editable-pointer {
border-style: solid;
border-width: 0 0 0 1px;
border-color: var(--grey-4);
padding-left: var(--spacing-xs);
}
.editable-pointer :global(svg) {
margin-right: var(--spacing-xs);
fill: var(--ink);
}
</style>

View file

@ -1,57 +0,0 @@
<script>
import { View } from "svench";
import Select from "./Select.svelte";
import DataList from "./DataList.svelte";
import Spacer from "../Spacer/Spacer.svelte"
const options = ["Chocolate", "Vanilla", "Strawberry Cheesecake"];
</script>
<View name="default">
<DataList name="Test" label="Flavour">
{#each options as option}
<option value={option}>{option}</option>
{/each}
</DataList>
</View>
<View name="secondary">
<DataList secondary name="Test" label="Flavour">
{#each options as option}
<option value={option}>{option}</option>
{/each}
</DataList>
</View>
<View name="outline">
<DataList outline name="Test" label="Flavour">
{#each options as option}
<option value={option}>{option}</option>
{/each}
</DataList>
</View>
<View name="disabled">
<DataList disabled name="Test" label="Flavour">
{#each options as option}
<option value={option}>{option}</option>
{/each}
</DataList>
</View>
<View name="thin">
<DataList thin name="Test" label="Flavour">
{#each options as option}
<option value={option}>{option}</option>
{/each}
</DataList>
</View>
<View name="extraThin">
<DataList extraThin name="Test" label="Flavour">
{#each options as option}
<option value={option}>{option}</option>
{/each}
</DataList>
</View>

View file

@ -0,0 +1,29 @@
<script>
import Field from "./Field.svelte"
import DatePicker from "./Core/DatePicker.svelte"
import { createEventDispatcher } from "svelte"
export let value = null
export let label = null
export let labelPosition = "above"
export let disabled = false
export let error = null
export let enableTime = true
export let placeholder = null
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
</script>
<Field {label} {labelPosition} {disabled} {error}>
<DatePicker
{error}
{disabled}
{value}
{placeholder}
{enableTime}
on:change={onChange} />
</Field>

View file

@ -0,0 +1,42 @@
<script>
import "@spectrum-css/fieldlabel/dist/index-vars.css"
import FieldLabel from "./FieldLabel.svelte"
export let id = null
export let label = null
export let labelPosition = "above"
export let disabled = false
export let error = null
</script>
<div class="spectrum-Form-item" class:above={labelPosition === 'above'}>
{#if label}
<FieldLabel forId={id} {label} position={labelPosition} />
{/if}
<div class="spectrum-Form-itemField">
<slot />
{#if error}
<div class="error">{error}</div>
{/if}
</div>
</div>
<style>
.spectrum-Form-item.above {
display: flex;
flex-direction: column;
}
.spectrum-Form-itemField {
position: relative;
width: 100%;
}
.error {
color: var(
--spectrum-semantic-negative-color-default,
var(--spectrum-global-color-red-500)
);
font-size: var(--spectrum-global-dimension-font-size-75);
margin-top: var(--spectrum-global-dimension-size-75);
}
</style>

View file

@ -0,0 +1,26 @@
<script>
import "@spectrum-css/fieldlabel/dist/index-vars.css"
export let forId
export let label
export let position = "above"
$: className = position === "above" ? "" : `spectrum-FieldLabel--${position}`
</script>
<label
for={forId}
class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel ${className}`}>
{label || ''}
</label>
<style>
label {
white-space: nowrap;
}
.spectrum-FieldLabel--right,
.spectrum-FieldLabel--left {
padding-right: var(--spectrum-global-dimension-size-200);
}
</style>

View file

@ -1,190 +1,33 @@
<script> <script>
import Field from "./Field.svelte"
import TextField from "./Core/TextField.svelte"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import Button from "../Button/Button.svelte"
import Label from "../Styleguide/Label.svelte"
const dispatch = createEventDispatcher()
export let name = undefined export let value = null
export let label = undefined export let label = null
export let outline = false export let labelPosition = "above"
export let presentation = false export let placeholder = null
export let thin = false export let type = "text"
export let extraThin = false
export let large = false
export let border = false
export let edit = false
export let disabled = false export let disabled = false
export let type = undefined export let readonly = false
export let placeholder = "" export let error = null
export let value = ""
export let error = false
export let validator = () => {}
// This section handles the edit mode and dispatching of things to the parent when saved const dispatch = createEventDispatcher()
let editMode = false const onChange = e => {
value = e.detail
const updateValue = e => { dispatch("change", e.detail)
if (type === "number") {
const num = parseFloat(e.target.value)
value = isNaN(num) ? "" : num
} else {
value = e.target.value
}
}
const save = () => {
editMode = false
dispatch("save", value)
}
const enableEdit = () => {
editMode = true
} }
</script> </script>
<div class="container"> <Field {label} {labelPosition} {disabled} {error}>
{#if label || edit} <TextField
<div class="label-container"> {error}
{#if label} {disabled}
<Label extraSmall grey forAttr={name}>{label}</Label> {readonly}
{/if} {value}
{#if edit} {placeholder}
<div class="controls">
<Button small secondary disabled={editMode} on:click={enableEdit}>
Edit
</Button>
<Button small blue disabled={!editMode} on:click={save}>Save</Button>
</div>
{/if}
</div>
{/if}
<input
class:outline
class:presentation
class:thin
class:extraThin
class:large
class:border
on:change
on:input
on:change={updateValue}
on:input={updateValue}
on:blur={updateValue}
use:validator
disabled={disabled || (edit && !editMode)}
value={value == null ? '' : value}
{type} {type}
{name} on:change={onChange}
{placeholder} /> on:click
{#if error} on:input />
<div class="error">{error}</div> </Field>
{/if}
</div>
<style>
.container {
min-width: 0;
display: flex;
flex-direction: column;
}
.label-container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
margin-bottom: var(--spacing-s);
}
.label-container :global(label) {
margin-bottom: 0;
}
.controls {
align-items: center;
display: grid;
grid-template-columns: auto auto;
grid-gap: 12px;
margin-left: auto;
padding-left: 12px;
}
.controls :global(button) {
min-width: 100px;
font-size: var(--font-size-s);
border-radius: var(--rounded-small);
}
input {
min-width: 0;
box-sizing: border-box;
color: var(--ink);
font-size: var(--font-size-s);
border-radius: var(--border-radius-s);
border: none;
background-color: var(--grey-2);
padding: var(--spacing-m);
margin: 0;
outline: none;
font-family: var(--font-sans);
border: var(--border-transparent);
transition: all 0.2s ease-in-out;
}
input.presentation {
background-color: var(--background);
border: var(--background) 2px solid;
}
input.presentation:hover {
background-color: var(--grey-2);
border: var(--grey-4) 2px solid;
}
input.thin {
font-size: var(--font-size-xs);
}
input.extraThin {
font-size: var(--font-size-xs);
padding: var(--spacing-s) var(--spacing-m);
}
input.large {
font-size: var(--font-size-m);
padding: var(--spacing-l);
}
input.border {
border: var(--border-grey-2);
}
input.border:active {
border: var(--border-blue);
}
input.border:focus {
border: var(--border-blue);
}
input.outline {
border: var(--border-light-2);
background: var(--background);
}
input.outline:active {
border: var(--border-blue);
}
input.outline:focus {
border: var(--border-blue);
}
input:hover {
border: var(--grey-4) 2px solid;
}
input::placeholder {
color: var(--grey-6);
}
input:focus {
border: var(--border-blue);
}
input:disabled {
background: var(--grey-4);
color: var(--grey-6);
}
.error {
margin-top: 10px;
font-size: var(--font-size-xs);
font-family: var(--font-sans);
line-height: 1.17;
color: var(--red);
}
</style>

View file

@ -1,62 +0,0 @@
<script>
import { View } from "svench";
import Input from "./Input.svelte";
import Button from "../Button/Button.svelte";
</script>
<View name="default">
<Input placeholder="Enter your name" label="Name" />
</View>
<View name="presentation">
<Input presentation placeholder="Enter your name" label="Name" />
</View>
<View name="outline">
<Input outline placeholder="Enter your name" label="Name" />
</View>
<View name="disabled">
<Input disabled placeholder="Enter your name" label="Name" />
</View>
<View name="disabled with value">
<Input value="Some text" disabled placeholder="Enter your name" label="Name" />
</View>
<View name="thin">
<Input thin placeholder="Enter your name" label="Name" />
</View>
<View name="extraThin">
<Input extraThin placeholder="Enter your name" label="Name" />
</View>
<View name="large">
<Input large placeholder="Enter your name" label="Name" />
</View>
<View name="border">
<Input border presentation placeholder="Enter your name" label="Name" />
</View>
<View name="number">
<Input type="number" placeholder="Enter your age" label="Age" />
</View>
<View name="with edit buttons">
<Input
thin
edit
placeholder="Enter your name"
label="Name"
on:save={console.log} />
</View>
<View name="with error message">
<Input
placeholder="Enter your name"
label="Name"
error="This is an error message!"
on:save={console.log} />
</View>

View file

@ -1,324 +1,35 @@
<script> <script>
import Portal from "svelte-portal"
import { afterUpdate } from "svelte"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { fly } from "svelte/transition" import Multiselect from "./Core/Multiselect.svelte"
import Label from "../Styleguide/Label.svelte" import Field from "./Field.svelte"
const xPath =
"M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z"
import positionDropdown from "../Actions/position_dropdown"
import clickOutside from "../Actions/click_outside"
const dispatch = createEventDispatcher()
export let value = [] export let value = []
export let label = undefined export let label = null
export let align = "left"
export let secondary = false
export let outline = false
export let disabled = false export let disabled = false
export let placeholder = undefined export let readonly = false
export let extraThin = false export let labelPosition = "above"
export let error = null
export let placeholder = null
export let options = []
export let getOptionLabel = option => option
export let getOptionValue = option => option
let options = [] const dispatch = createEventDispatcher()
let optionsVisible = false const onChange = e => {
let slot value = e.detail
let anchor dispatch("change", e.detail)
$: lookupMap = mapValues(value)
$: selectedOptions = options.filter(option => lookupMap[option.value])
afterUpdate(() => {
// Update available options
const domOptions = Array.from(slot.querySelectorAll("option"))
options = domOptions.map(option => ({
value: option.value,
name: option.textContent,
}))
})
function mapValues(value) {
let map = {}
if (value) {
value.forEach(option => {
map[option] = true
})
}
return map
}
function add(val) {
value = [...value, val]
dispatch("change", value)
}
function remove(val) {
value = value.filter(option => option !== val)
dispatch("change", value)
}
function showOptions(show) {
optionsVisible = show
}
function handleClick() {
showOptions(!optionsVisible)
}
function handleOptionMousedown(e) {
const value = e.target.dataset.value
if (value == null) {
return
}
if (lookupMap[value]) {
remove(value)
} else {
add(value)
}
} }
</script> </script>
{#if label} <Field {label} {labelPosition} {disabled} {error}>
<Label extraSmall grey>{label}</Label> <Multiselect
{/if} {error}
<div class="multiselect" bind:this={anchor}> {disabled}
<div class="tokens-wrapper"> {value}
<div {options}
class="tokens" {placeholder}
class:outline {getOptionLabel}
class:disabled {getOptionValue}
class:secondary on:change={onChange}
class:extraThin on:click />
class:optionsVisible </Field>
on:click|self={handleClick}
class:empty={!value || !value.length}>
{#each selectedOptions as option}
<div
class="token"
class:extraThin
data-id={option.value}
on:click|self={handleClick}>
<span>{option.name}</span>
<div
class="token-remove"
title="Remove {option.name}"
on:click={() => remove(option.value)}>
<svg
class="icon-clear"
xmlns="http://www.w3.org/2000/svg"
width="18"
height="18"
viewBox="0 0 24 24">
<path d={xPath} />
</svg>
</div>
</div>
{/each}
{#if !value || !value.length}
{#if placeholder && placeholder.length}
<div class:disabled class="placeholder">{placeholder}</div>
{:else}
<div class="placeholder">&nbsp;</div>
{/if}
{/if}
</div>
</div>
<select bind:this={slot} type="multiple" class="hidden">
<slot />
</select>
{#if optionsVisible}
<Portal>
<ul
class="options"
use:positionDropdown={{ anchor, align }}
use:clickOutside={() => showOptions(false)}
transition:fly={{ duration: 200, y: 5 }}
on:mousedown|preventDefault={handleOptionMousedown}>
{#each options as option}
<li
class:selected={lookupMap[option.value]}
data-value={option.value}>
{option.name}
</li>
{/each}
{#if !options.length}
<li class="no-results">No results</li>
{/if}
</ul>
</Portal>
{/if}
</div>
<style>
.multiselect {
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
font-family: var(--font-sans);
min-width: 0;
}
.multiselect:hover {
border-bottom-color: hsl(0, 0%, 50%);
}
.tokens-wrapper {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
flex: 0 1 auto;
}
.tokens {
align-items: center;
display: flex;
flex-wrap: wrap;
position: relative;
width: 0;
flex: 1 1 auto;
background-color: var(--background);
border-radius: var(--border-radius-m);
padding: 0 var(--spacing-m) calc(var(--spacing-m) - var(--spacing-xs))
calc(var(--spacing-m) / 2);
border: var(--border-transparent);
}
.tokens.disabled {
background-color: var(--grey-4);
pointer-events: none;
}
.tokens.outline {
border: var(--border-dark);
}
.tokens.secondary {
background-color: var(--grey-2);
}
.tokens.extraThin {
padding: 0 var(--spacing-m) calc(var(--spacing-s) - var(--spacing-xs))
calc(var(--spacing-m) / 2);
}
.tokens:hover {
cursor: pointer;
}
.tokens.optionsVisible {
border: var(--border-blue);
}
.tokens.empty {
padding: var(--spacing-m);
font-size: var(--font-size-xs);
user-select: none;
}
.tokens.empty.extraThin {
padding: var(--spacing-s) var(--spacing-m);
}
.tokens::after {
width: 100%;
left: 0;
}
.token {
font-size: var(--font-size-xs);
background-color: var(--ink);
color: var(--background);
border-radius: var(--border-radius-l);
display: flex;
flex-direction: row;
align-items: center;
margin: calc(var(--spacing-m) - var(--spacing-xs)) 0 0
calc(var(--spacing-m) / 2);
max-height: 1.3rem;
padding: var(--spacing-xs) var(--spacing-s);
transition: background-color 0.3s;
white-space: nowrap;
overflow: hidden;
}
.token.extraThin {
margin: calc(var(--spacing-s) - var(--spacing-xs)) 0 0
calc(var(--spacing-m) / 2);
}
.token span {
pointer-events: none;
user-select: none;
flex: 1 1 auto;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.token .token-remove {
align-items: center;
background-color: var(--grey-7);
border-radius: 50%;
display: flex;
justify-content: center;
height: 1rem;
width: 1rem;
margin: calc(-1 * var(--spacing-xs)) 0 calc(-1 * var(--spacing-xs))
var(--spacing-xs);
flex: 0 0 auto;
}
.token path {
fill: var(--background);
}
.token .token-remove:hover {
background-color: var(--grey-6);
cursor: pointer;
}
.placeholder {
pointer-events: none;
color: var(--ink);
}
.placeholder.disabled {
color: var(--grey-6);
}
.icon-clear path {
fill: white;
}
.options {
left: 0;
list-style: none;
margin-block-end: 0;
margin-block-start: 0;
overflow-y: auto;
padding-inline-start: 0;
position: absolute;
border: var(--border-dark);
border-radius: var(--border-radius-m);
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.15);
margin: var(--spacing-xs) 0;
padding: var(--spacing-s) 0;
background-color: var(--background);
max-height: 200px;
}
li {
cursor: pointer;
padding: var(--spacing-s) var(--spacing-m);
font-size: var(--font-size-xs);
color: var(--ink);
}
li.selected {
background-color: var(--blue);
color: white;
}
li:not(.selected):hover {
background-color: var(--grey-1);
}
li.no-results:hover {
background-color: white;
cursor: initial;
}
.hidden {
height: 0;
overflow: hidden;
visibility: hidden;
padding: 0;
margin: 0;
border: 0;
outline: 0;
}
</style>

View file

@ -1,63 +0,0 @@
<script>
import { View } from "svench";
import Multiselect from "./Multiselect.svelte";
const options = ["Red", "Blue", "Yellow", "Green", "Pink", "Very long color name to show text wrapping"];
</script>
<View name="default">
<Multiselect name="Test" label="Colours" placeholder="Choose some colours">
{#each options as option}
<option value={option}>{option}</option>
{/each}
</Multiselect>
</View>
<View name="right aligned">
<div class="max-width">
<Multiselect align="right" name="Test" label="Colours" placeholder="Choose some colours">
{#each options as option}
<option value={option}>{option}</option>
{/each}
</Multiselect>
</div>
</View>
<View name="secondary">
<Multiselect name="Test" label="Colours" secondary placeholder="Choose some colours">
{#each options as option}
<option value={option}>{option}</option>
{/each}
</Multiselect>
</View>
<View name="outline">
<Multiselect name="Test" label="Colours" outline placeholder="Choose some colours">
{#each options as option}
<option value={option}>{option}</option>
{/each}
</Multiselect>
</View>
<View name="disabled">
<Multiselect name="Test" label="Colours" disabled placeholder="Choose some colours">
{#each options as option}
<option value={option}>{option}</option>
{/each}
</Multiselect>
</View>
<View name="extraThin">
<Multiselect name="Test" label="Colours" extraThin placeholder="Choose some colours">
{#each options as option}
<option value={option}>{option}</option>
{/each}
</Multiselect>
</View>
<style>
.max-width {
align-self: flex-end;
max-width: 150px;
}
</style>

View file

@ -1,140 +0,0 @@
<script>
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
export let value
export let group
export let name
export let disabled = false
function handleChange() {
if (disabled) return
group = value
dispatch("change", group)
}
</script>
<div class="container">
<input
{disabled}
on:change={handleChange}
{value}
bind:group
type="radio"
{name}
class="checkbox"
id={value} />
<div class="checkbox-container" on:click={handleChange}>
<div class:disabled class="check-div" class:checked={group === value}>
<div class="tick_mark" />
</div>
</div>
<slot />
</div>
<style>
.container {
display: flex;
gap: var(--spacing-s);
}
.checkbox-container {
position: relative;
z-index: 0;
}
.checkbox {
display: none;
}
.check-div {
position: relative;
width: 20px;
height: 20px;
background-color: var(--grey-2);
cursor: pointer;
transition: 0.2s ease transform, 0.2s ease background-color,
0.2s ease box-shadow;
overflow: hidden;
border-radius: 4px;
}
.check-div:before {
content: "";
position: absolute;
top: 50%;
right: 0;
left: 0;
width: 12px;
height: 12px;
margin: 0 auto;
background-color: var(--background);
transform: translateY(-50%);
transition: 0.2s ease width, 0.2s ease height;
border-radius: 2px;
}
.check-div:active {
transform: translateY(-50%) scale(0.9);
}
.tick_mark {
position: absolute;
top: 50%;
left: 6px;
width: 5px;
height: 4px;
margin: 0 auto;
transform: rotateZ(-40deg);
}
.tick_mark:before,
.tick_mark:after {
content: "";
position: absolute;
background-color: var(--ink);
border-radius: 2px;
opacity: 0;
transition: 0.2s ease transform, 0.2s ease opacity;
}
.tick_mark:before {
left: 0;
bottom: 0;
width: 2px;
height: 6px;
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.23);
transform: translateY(-68px);
}
.tick_mark:after {
left: 0;
bottom: 0;
width: 12px;
height: 2px;
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.23);
transform: translateX(78px);
}
.check-div.disabled:active {
transform: none;
}
.checked {
background-color: var(--grey-2);
}
.checked.disabled {
background-color: var(--grey-5);
}
.checked:before {
width: 0;
height: 0;
}
.checked .tick_mark:before,
.checked .tick_mark:after {
transform: translate(0);
opacity: 1;
}
</style>

View file

@ -1,64 +0,0 @@
<script>
import { View } from "svench";
import Radio from "./Radio.svelte";
let selected = 'Cookies and cream'
let selected2 = 'Mint choc chip'
let menu = [
'Cookies and cream',
'Mint choc chip',
'Raspberry ripple'
];
</script>
## Multiple checkboxes
Use an array and an each block to use the radio button.
```svelte
<script>
let selected = 'Cookies and cream'
let selected2 = 'Cookies and cream'
let menu = [
'Cookies and cream',
'Mint choc chip',
'Raspberry ripple'
];
</script>
{#each menu as flavour}
<Radio name="Ice Cream Flavour" value={flavour} bind:group={selected} label={flavour} showLabel/>
{/each}
```
<View name="Multiple radio buttons">
<div class="container">
{#each menu as flavour}
<Radio name="Ice Cream Flavour" value={flavour} bind:group={selected}>
<label for={flavour}>{flavour}</label>
</Radio>
{/each}
</div>
</View>
<View name="Disabled Radio inputs">
<div class="container">
{#each menu as flavour}
<Radio disabled name="Ice Cream Flavour" value={flavour} bind:group={selected2}>
<label for={flavour}>{flavour}</label>
</Radio>
{/each}
</div>
</View>
<style>
label {
display: grid;
place-items: center;
}
.container {
display: grid;
grid-gap: 10px;
}
</style>

View file

@ -0,0 +1,37 @@
<script>
import Field from "./Field.svelte"
import RadioGroup from "./Core/RadioGroup.svelte"
import { createEventDispatcher } from "svelte"
export let value = null
export let label = null
export let disabled = false
export let labelPosition = "above"
export let error = null
export let options = []
export let getOptionLabel = option => extractProperty(option, "label")
export let getOptionValue = option => extractProperty(option, "value")
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
const extractProperty = (value, property) => {
if (value && typeof value === "object") {
return value[property]
}
return value
}
</script>
<Field {label} {labelPosition} {disabled} {error}>
<RadioGroup
{error}
{disabled}
{value}
{options}
{getOptionLabel}
{getOptionValue}
on:change={onChange} />
</Field>

View file

@ -1,59 +0,0 @@
<script>
import * as Quill from "quill"
import * as MarkdownIt from "markdown-it"
import TurndownService from "turndown"
import { onMount } from "svelte"
import "quill/dist/quill.snow.css"
const convertMarkdown = new MarkdownIt()
convertMarkdown.set({
html: true,
})
const turndownService = new TurndownService()
export let value = ""
export let options = null
export let width = 400
let quill
let container
let defaultOptions = {
modules: {
toolbar: [
[{ header: [1, 2, 3, false] }],
["bold", "italic", "underline", "strike"],
],
},
placeholder: "Type something...",
theme: "snow", // or 'bubble'
}
let mergedOptions = { ...defaultOptions, ...options }
const updateContent = () => {
value = turndownService.turndown(quill.container.firstChild.innerHTML)
}
onMount(() => {
quill = new Quill(container, mergedOptions)
if (value)
quill.clipboard.dangerouslyPasteHTML(convertMarkdown.render(value + "\n"))
quill.on("text-change", updateContent)
return () => {
quill.off("text-change", updateContent)
}
})
</script>
<svelte:head>
{#if mergedOptions.theme !== 'snow'}
<link
rel="stylesheet"
href="//cdn.quilljs.com/1.3.6/quill.{mergedOptions.theme}.css" />
{/if}
</svelte:head>
<div style="width: {width}px">
<div bind:this={container} />
</div>

View file

@ -1,40 +0,0 @@
<script>
import { View } from "svench";
import RichText from "./RichText.svelte";
const options = { placeholder: "this is not the default value!" };
let value;
</script>
### Rich Text Component
This component uses the QuillJS library to add Rich Text editing functionality.
It exposes a <code>content</code> variable that you can bind to in order to get Markdown out of the component.
As well as the content you can also pass in an option object that looks like so:
```js
let options = {
modules: {
toolbar: [
[{ header: [1, 2, 3, false] }],
['bold', 'italic', 'underline', 'strike']
]
},
placeholder: 'Type something...',
theme: 'snow'
}
```
<View name="default">
<RichText bind:value />
</View>
<View name="passing in Markdown">
<RichText value="# This is an h1 heading!" />
</View>
<View name="passing in custom options">
<RichText {options} />
</View>

View file

@ -0,0 +1,27 @@
<script>
import Field from "./Field.svelte"
import Search from "./Core/Search.svelte"
import { createEventDispatcher } from "svelte"
export let value = null
export let label = null
export let labelPosition = "above"
export let placeholder = null
export let disabled = false
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
</script>
<Field {label} {labelPosition} {disabled}>
<Search
{disabled}
{value}
{placeholder}
on:change={onChange}
on:click
on:input />
</Field>

View file

@ -1,95 +1,42 @@
<script> <script>
import Icon from "../Icons/Icon.svelte" import Field from "./Field.svelte"
import Label from "../Styleguide/Label.svelte" import Select from "./Core/Select.svelte"
import { createEventDispatcher } from "svelte"
export let value = "" export let value = null
export let name = undefined
export let label = undefined export let label = undefined
export let thin = false
export let extraThin = false
export let secondary = false
export let outline = false
export let disabled = false export let disabled = false
export let readonly = false
export let labelPosition = "above"
export let error = null
export let placeholder = "Choose an option"
export let options = []
export let getOptionLabel = option => extractProperty(option, "label")
export let getOptionValue = option => extractProperty(option, "value")
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
const extractProperty = (value, property) => {
if (value && typeof value === "object") {
return value[property]
}
return value
}
</script> </script>
<div> <Field {label} {labelPosition} {disabled} {error}>
{#if label} <Select
<Label extraSmall grey forAttr={name}>{label}</Label> {error}
{/if} {disabled}
<div class="relative"> {readonly}
<select {value}
{name} {options}
class:thin {placeholder}
class:extraThin {getOptionLabel}
class:secondary {getOptionValue}
class:outline on:change={onChange}
{disabled} on:click />
on:change </Field>
bind:value>
<slot />
</select>
<div class="pointer">
<Icon name="arrowdown" />
</div>
</div>
</div>
<style>
select {
font-family: var(--font-sans);
display: block !important;
width: 100% !important;
border-radius: var(--border-radius-s);
border: none;
text-align: left;
color: var(--ink);
font-size: var(--font-size-s);
padding: var(--spacing-m) 2rem var(--spacing-m) var(--spacing-m) !important;
appearance: none !important;
-webkit-appearance: none !important;
-moz-appearance: none !important;
align-items: center;
white-space: pre;
outline: none;
border: var(--border-transparent);
background-color: var(--background);
}
select.thin {
padding: var(--spacing-m);
font-size: var(--font-size-xs);
}
select.extraThin {
padding: var(--spacing-s) 2rem var(--spacing-s) var(--spacing-m) !important;
font-size: var(--font-size-xs);
}
select.secondary {
background: var(--grey-2);
}
select.outline {
border: var(--border-light-2);
}
select:focus {
border: var(--border-blue);
}
select:disabled {
background: var(--grey-4);
color: var(--grey-6);
}
.relative {
position: relative !important;
display: block;
}
.pointer {
right: 0 !important;
top: 0 !important;
bottom: 0 !important;
position: absolute !important;
pointer-events: none !important;
padding-left: 0.5rem !important;
align-items: center !important;
display: flex !important;
color: var(--ink);
}
</style>

View file

@ -1,62 +0,0 @@
<script>
import { View } from "svench";
import Select from "./Select.svelte";
import Spacer from "../Spacer/Spacer.svelte"
const options = ["Chocolate", "Vanilla", "Strawberry Cheesecake"];
</script>
<View name="default">
<Select name="Test" label="Flavour">
<option value="">Choose an option</option>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</Select>
</View>
<View name="secondary">
<Select secondary name="Test" label="Flavour">
<option value="">Choose an option</option>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</Select>
</View>
<View name="outline">
<Select outline name="Test" label="Flavour">
<option value="">Choose an option</option>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</Select>
</View>
<View name="disabled">
<Select disabled name="Test" label="Flavour">
<option value="">Choose an option</option>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</Select>
</View>
<View name="thin">
<Select thin name="Test" label="Flavour">
<option value="">Choose an option</option>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</Select>
</View>
<View name="extraThin">
<Select extraThin name="Test" label="Flavour">
<option value="">Choose an option</option>
{#each options as option}
<option value={option}>{option}</option>
{/each}
</Select>
</View>

View file

@ -1,88 +0,0 @@
<script>
import Label from "../Styleguide/Label.svelte"
export let label
export let min = 0
export let max = 100
export let step = 1
export let value
export let showValue = false
export let showRange = false
</script>
<div>
{#if label}
<Label extraSmall grey>
{label}
{#if showValue && value != null}({value}){/if}
</Label>
{/if}
<div class="container">
{#if showRange && min != null}<span>{min}</span>{/if}
<input type="range" bind:value {min} {max} {step} />
{#if showRange && max != null}<span>{max}</span>{/if}
</div>
</div>
<style>
.container {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-s);
}
span {
flex: 0 0 auto;
color: var(--grey-5);
font-family: var(--font-sans);
font-size: var(--font-size-xs);
font-weight: 400;
}
input[type="range"] {
width: 100%;
margin: 0;
background-color: transparent;
-webkit-appearance: none;
flex: 1 1 auto;
}
input[type="range"]:focus {
outline: none;
}
input[type="range"]::-webkit-slider-runnable-track {
background: var(--grey-4);
border-radius: 9px;
width: 100%;
height: 18px;
cursor: pointer;
padding: 0 2px;
}
input[type="range"]::-webkit-slider-thumb {
width: 14px;
height: 14px;
background: white;
border-radius: 100%;
cursor: pointer;
-webkit-appearance: none;
border: none;
margin-top: 2px;
}
input[type="range"]::-moz-range-track {
background: var(--grey-4);
border-radius: 9px;
width: 100%;
height: 18px;
cursor: pointer;
padding: 0 2px;
}
input[type="range"]::-moz-range-thumb {
width: 14px;
height: 14px;
background: white;
border-radius: 100%;
cursor: pointer;
border: none;
}
</style>

View file

@ -1,26 +0,0 @@
<script>
import { View } from "svench";
import Slider from "./Slider.svelte";
</script>
<View name="default">
<Slider label=Quantity value="50" />
</View>
<View name="show value">
<Slider label="Quantity" value="50" showValue />
</View>
<View name="show range">
<Slider label="Quantity" value="25" showValue showRange min="0" max="100" />
</View>
<View name="custom min and max">
<Slider label="Quantity" value="350" showValue showRange min="50" max="500" />
</View>
<View name="custom step">
<Slider label="Quantity" value="25" step="25" showValue showRange min="0" max="100" />
</View>

View file

@ -1,132 +1,29 @@
<script> <script>
import Field from "./Field.svelte"
import TextArea from "./Core/TextArea.svelte"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import Button from "../Button/Button.svelte"
import Label from "../Styleguide/Label.svelte" export let value = null
import text_area_resize from "../Actions/autoresize_textarea.js" export let label = null
export let labelPosition = "above"
export let placeholder = null
export let disabled = false
export let error = null
export let getCaretPosition = null
const dispatch = createEventDispatcher() const dispatch = createEventDispatcher()
const onChange = e => {
export let name = false value = e.detail
export let label = false dispatch("change", e.detail)
export let thin = false
export let extraThin = false
export let edit = false
export let disabled = false
export let placeholder
export let validator = () => {}
export let value = ""
export const getCaretPosition = () => {
return { start: textarea.selectionStart, end: textarea.selectionEnd }
}
let textarea
// This section handles the edit mode and dispatching of things to the parent when saved
let editMode = false
const save = () => {
editMode = false
dispatch("save", value)
}
const enableEdit = () => {
editMode = true
} }
</script> </script>
<div class="container"> <Field {label} {labelPosition} {disabled} {error}>
{#if label || edit} <TextArea
<div class="label-container"> bind:getCaretPosition
{#if label} {error}
<Label extraSmall grey forAttr={name}>{label}</Label> {disabled}
{/if} {value}
{#if edit}
<div class="controls">
<Button small secondary disabled={editMode} on:click={enableEdit}>
Edit
</Button>
<Button small blue disabled={!editMode} on:click={save}>Save</Button>
</div>
{/if}
</div>
{/if}
<textarea
class:thin
class:extraThin
bind:value
bind:this={textarea}
on:change
disabled={disabled || (edit && !editMode)}
{placeholder} {placeholder}
{name} on:change={onChange} />
use:text_area_resize /> </Field>
</div>
<style>
.container {
min-width: 0;
display: flex;
flex-direction: column;
}
.label-container {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
margin-bottom: var(--spacing-s);
}
.label-container :global(label) {
margin-bottom: 0;
}
.controls {
align-items: center;
display: grid;
grid-template-columns: auto auto;
grid-gap: 12px;
margin-left: auto;
padding-left: 12px;
}
.controls :global(button) {
min-width: 100px;
font-size: var(--font-size-s);
border-radius: var(--rounded-small);
}
textarea {
min-width: 0;
color: var(--ink);
font-size: var(--font-size-s);
font-family: var(--font-sans);
border: none;
border-radius: var(--border-radius-s);
background-color: var(--grey-2);
padding: var(--spacing-m);
margin: 0;
border: var(--border-transparent);
outline: none;
}
textarea::placeholder {
color: var(--grey-6);
}
textarea.thin {
font-size: var(--font-size-xs);
}
textarea.extraThin {
font-size: var(--font-size-xs);
padding: var(--spacing-s) var(--spacing-m);
}
textarea:focus {
border: var(--border-blue);
}
textarea:disabled {
background: var(--grey-4);
}
textarea:disabled {
background: var(--grey-4);
}
textarea:disabled::placeholder {
color: var(--grey-6);
}
</style>

View file

@ -1,30 +0,0 @@
<script>
import { View } from "svench";
import TextArea from "./TextArea.svelte";
import Button from "../Button/Button.svelte";
</script>
<View name="default">
<TextArea placeholder="Enter your email text" label="Email Body" />
</View>
<View name="disabled">
<TextArea disabled placeholder="Enter your email text" label="Email Body" />
</View>
<View name="no label">
<TextArea placeholder="Enter your email text" />
</View>
<View name="thin">
<TextArea thin placeholder="Enter your email text" label="Email Body" />
</View>
<View name="extraThin">
<TextArea extraThin placeholder="Enter your email text" label="Email Body" />
</View>
<View name="with buttons">
<TextArea edit placeholder="Enter your email text" label="Email Body" />
</View>

View file

@ -1,110 +1,22 @@
<script> <script>
export let name = undefined import Field from "./Field.svelte"
export let text = "" import Switch from "./Core/Switch.svelte"
export let checked = false import { createEventDispatcher } from "svelte"
export let value = null
export let label = null
export let labelPosition = "above"
export let text = null
export let disabled = false export let disabled = false
export let screenreader = true export let error = null
export let thin = false
const dispatch = createEventDispatcher()
const onChange = e => {
value = e.detail
dispatch("change", e.detail)
}
</script> </script>
<label for={name} class="container"> <Field {label} {labelPosition} {disabled} {error}>
<div class="toggle"> <Switch {error} {disabled} {text} {value} on:change={onChange} />
<input </Field>
id={name}
{name}
type="checkbox"
class:screenreader
{disabled}
bind:checked
on:change />
<div class="track">
<div class="thumb" />
</div>
</div>
{#if text}<span class="text" class:thin>{text}</span>{/if}
</label>
<style>
.container {
display: flex;
align-items: center;
gap: var(--spacing-s);
}
.container:disabled {
background-color: var(--grey-2);
cursor: not-allowed;
}
.toggle {
position: relative;
display: inline-block;
align-self: center;
cursor: pointer;
-webkit-user-select: none;
background: transparent;
}
.track {
width: 32px;
height: 18px;
background-color: var(--grey-4);
border-radius: 9px;
transition-delay: 0.12s;
transition-duration: 0.2s;
transition-property: background;
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
position: relative;
}
.thumb {
cursor: pointer;
position: absolute;
transition-duration: 0.28s;
transition-property: all;
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
top: 2px;
left: 2px;
width: 14px;
height: 14px;
background-color: white;
border-radius: var(--border-radius-xl);
}
input[type="checkbox"]:checked ~ .track .thumb {
transform: translateX(14px);
}
input[type="checkbox"]:checked ~ .track {
background-color: var(--blue);
}
input[type="checkbox"]:disabled ~ .track {
background-color: var(--grey-4);
cursor: not-allowed;
}
input[type="checkbox"]:disabled ~ .track .thumb {
background-color: var(--grey-2);
cursor: not-allowed;
}
.screenreader {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
.text {
font-size: var(--font-size-s);
font-family: var(--font-sans);
cursor: pointer;
user-select: none;
color: var(--ink);
}
.text.thin {
font-size: var(--font-size-xs);
}
</style>

View file

@ -1,21 +0,0 @@
<script>
import { View } from "svench";
import Toggle from "./Toggle.svelte";
</script>
<View name="default">
<Toggle />
</View>
<View name="checked with text">
<Toggle text="Display on mobile?" />
</View>
<View name="thin">
<Toggle text="Display on mobile?" thin />
</View>
<View name="disabled">
<Toggle disabled={true} />
</View>

View file

@ -1,19 +1,30 @@
<script context="module"> <script context="module">
export const directions = ["n", "ne", "e", "se", "s", "sw", "w", "nw"] export const directions = ["n", "ne", "e", "se", "s", "sw", "w", "nw"]
</script> </script>
<script> <script>
export let direction = "n" export let direction = "n"
export let name = "Add" export let name = "Add"
export let hidden = true export let hidden = false
export let s = false export let s = false
export let m = false; export let m = false
export let l = false; export let l = false
export let xl = false export let xl = false
$: rotation = directions.indexOf(direction) * 45 $: rotation = directions.indexOf(direction) * 45
$: useDefault = ![s, m, l, xl].includes(true)
</script> </script>
<svg on:click class:spectrum-Icon--sizeS={s} class:spectrum-Icon--sizeM={m} class:spectrum-Icon--sizeL={l} class:spectrum-Icon--sizeXL={xl} class="spectrum-Icon" focusable="false" aria-hidden={hidden} aria-label="{name}" style={`transform: rotate(${rotation}deg)`}> <svg
<use xlink:href="#spectrum-icon-18-{name}" /> on:click
class:spectrum-Icon--sizeS={s}
class:spectrum-Icon--sizeM={m || useDefault}
class:spectrum-Icon--sizeL={l}
class:spectrum-Icon--sizeXL={xl}
class="spectrum-Icon"
focusable="false"
aria-hidden={hidden}
aria-label={name}
style={`transform: rotate(${rotation}deg)`}>
<use xlink:href="#spectrum-icon-18-{name}" />
</svg> </svg>

View file

@ -1,33 +0,0 @@
<script>
import Input from "../Form/Input.svelte"
export let categories = [
{
name: "Customers List - Data Row",
items: [
{ name: "Name", id: "chjaHICHc82h2" },
{ name: "Created", id: "chjaHICgr56Hc82h2" },
{ name: "Status", id: "chjaHICHc8646462h2" },
],
},
{
name: "Home Screen Components",
items: [{ name: "Title", id: "chjaHICHc82h2" }],
},
]
</script>
<div class="container">
<Input thin placeholder="Search" />
{#each categories as { name, items }}
<div class="title">{name}</div>
<ul>
{#each items as { name, id }}
<li>{name}</li>
{/each}
</ul>
{/each}
</div>
<style>
</style>

View file

@ -1,8 +0,0 @@
<script>
import Search from "./Search.svelte";
import { View } from "svench";
</script>
<View name="Name">
<Search />
</View>

View file

@ -5,8 +5,9 @@
import { fade, fly } from "svelte/transition" import { fade, fly } from "svelte/transition"
import Portal from "svelte-portal" import Portal from "svelte-portal"
import Context from "../context" import Context from "../context"
const dispatch = createEventDispatcher() import clickOutside from "../Actions/click_outside"
const dispatch = createEventDispatcher()
let visible = false let visible = false
$: dispatch(visible ? "show" : "hide") $: dispatch(visible ? "show" : "hide")
@ -31,8 +32,8 @@
} }
async function focusFirstInput(node) { async function focusFirstInput(node) {
const inputs = node.querySelectorAll('input') const inputs = node.querySelectorAll("input")
if (inputs) { if (inputs?.length) {
await tick() await tick()
inputs[0].focus() inputs[0].focus()
} }
@ -45,12 +46,58 @@
{#if visible} {#if visible}
<Portal target=".modal-container"> <Portal target=".modal-container">
<div use:focusFirstInput class="spectrum-Underlay is-open" transition:fade={{ duration: 200 }} on:click|self={hide}> <div
<div class="spectrum-Modal-wrapper"> class="spectrum-Underlay is-open"
<div class="spectrum-Modal is-open" transition:fly={{ y: 30, duration: 200 }}> transition:fade={{ duration: 200 }}
<slot /> on:mousedown|self={hide}>
<div class="modal-wrapper" on:mousedown|self={hide}>
<div class="modal-inner-wrapper" on:mousedown|self={hide}>
<div
use:focusFirstInput
class="spectrum-Modal is-open"
transition:fly={{ y: 30, duration: 200 }}>
<slot />
</div>
</div> </div>
</div> </div>
</div> </div>
</Portal> </Portal>
{/if} {/if}
<style>
.spectrum-Underlay {
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
z-index: 999;
overflow: auto;
overflow-x: hidden;
}
.modal-wrapper {
flex: 1 1 auto;
display: flex;
flex-direction: row;
-moz-box-pack: center;
justify-content: center;
align-items: flex-start;
max-height: 100%;
}
.modal-inner-wrapper {
flex: 1 1 auto;
display: flex;
flex-direction: row;
-moz-box-pack: center;
justify-content: center;
align-items: flex-start;
width: 0;
}
.spectrum-Modal {
overflow: visible;
max-height: none;
margin: 40px 0;
transform: none;
}
</style>

View file

@ -1,4 +1,5 @@
<script> <script>
import "@spectrum-css/dialog/dist/index-vars.css"
import { getContext } from "svelte" import { getContext } from "svelte"
import Button from "../Button/Button.svelte" import Button from "../Button/Button.svelte"
import Heading from "../Typography/Heading.svelte" import Heading from "../Typography/Heading.svelte"
@ -29,36 +30,38 @@
} }
</script> </script>
<div class="spectrum-Dialog spectrum-Dialog--{size}" role="dialog" tabindex="-1" aria-modal="true"> <div
class="spectrum-Dialog spectrum-Dialog--{size}"
role="dialog"
tabindex="-1"
aria-modal="true">
<div class="spectrum-Dialog-grid"> <div class="spectrum-Dialog-grid">
<Heading m h2>{title}</Heading> <h1 class="spectrum-Dialog-heading spectrum-Dialog-heading--noHeader">
<Divider m /> {title}
</h1>
<Divider m />
<!-- TODO: Remove content-grid class once Layout components are in bbui --> <!-- TODO: Remove content-grid class once Layout components are in bbui -->
<section class="spectrum-Dialog-content content-grid"> <section class="spectrum-Dialog-content content-grid">
<slot /> <slot />
</section> </section>
{#if showCancelButton || showConfirmButton} {#if showCancelButton || showConfirmButton}
<div class="spectrum-ButtonGroup spectrum-Dialog-buttonGroup spectrum-Dialog-buttonGroup--noFooter"> <div
<!-- <footer class="footer-content"> class="spectrum-ButtonGroup spectrum-Dialog-buttonGroup spectrum-Dialog-buttonGroup--noFooter">
<slot name="footer" /> <slot name="footer" />
</footer> --> {#if showCancelButton}
<div class="spectrum-ButtonGroup-item"> <Button group secondary on:click={hide}>{cancelText}</Button>
<slot name="footer" /> {/if}
{#if showCancelButton} {#if showConfirmButton}
<Button secondary on:click={hide}>{cancelText}</Button> <Button
{/if} group
{#if showConfirmButton}
<Button
cta cta
primary {...$$restProps}
{...$$restProps} disabled={confirmDisabled}
disabled={confirmDisabled} on:click={confirm}>
on:click={confirm}> {confirmText}
{confirmText} </Button>
</Button> {/if}
{/if}
</div>
</div> </div>
{/if} {/if}
{#if showCloseIcon} {#if showCloseIcon}
@ -69,7 +72,6 @@
</div> </div>
</div> </div>
<style> <style>
.content-grid { .content-grid {
display: grid; display: grid;
@ -78,9 +80,14 @@
color: var(--ink); color: var(--ink);
} }
h1 { .spectrum-Dialog-content {
font-weight: normal; overflow: visible;
} }
.spectrum-Dialog-buttonGroup {
gap: var(--spectrum-global-dimension-static-size-200);
}
.close-icon { .close-icon {
position: absolute; position: absolute;
top: 15px; top: 15px;
@ -95,27 +102,4 @@
.close-icon :global(svg) { .close-icon :global(svg) {
margin-right: 0; margin-right: 0;
} }
footer {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
gap: var(--spacing-m);
}
.footer-content {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
}
.buttons {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
gap: var(--spacing-m);
}
</style> </style>

View file

@ -30,7 +30,6 @@
<Portal> <Portal>
<div <div
tabindex="0" tabindex="0"
class:open
use:positionDropdown={{ anchor, align }} use:positionDropdown={{ anchor, align }}
use:clickOutside={hide} use:clickOutside={hide}
on:keydown={handleEscape} on:keydown={handleEscape}
@ -39,30 +38,3 @@
</div> </div>
</Portal> </Portal>
{/if} {/if}
<style>
.menu-container {
position: fixed;
margin-top: var(--spacing-xs);
padding: var(--spacing-xl);
outline: none;
box-sizing: border-box;
opacity: 0;
min-width: 400px;
z-index: 2;
color: var(--ink);
height: fit-content !important;
border: var(--border-dark);
border-radius: var(--border-radius-m);
transform: scale(0);
transition: opacity 0.13s linear, transform 0.12s cubic-bezier(0, 0, 0.2, 1);
overflow-y: auto;
background-color: var(--background);
box-shadow: 0 5px 12px rgba(0, 0, 0, 0.15);
}
.open {
transform: scale(1);
opacity: 1;
}
</style>

View file

@ -1,69 +1,14 @@
<script> <script>
export let forAttr = "", import "@spectrum-css/fieldlabel/dist/index-vars.css"
extraSmall = false,
small = false,
medium = false,
large = false,
extraLarge = false,
white = false,
grey = false,
black = false
</script> </script>
<label <label
class="bb-label" class={`spectrum-FieldLabel spectrum-FieldLabel--sizeM spectrum-Form-itemLabel`}>
class:extraSmall
class:small
class:medium
class:large
class:extraLarge
class:white
class:grey
class:black
for={forAttr}>
<slot /> <slot />
</label> </label>
<style> <style>
.bb-label { label {
font-family: var(--font-sans); white-space: nowrap;
font-weight: 500;
text-rendering: var(--text-render);
color: var(--ink);
font-size: var(--font-size-s);
margin-bottom: var(--spacing-s);
display: block;
}
.extraSmall {
font-size: var(--font-size-xs);
}
.small {
font-size: var(--font-size-s);
}
.medium {
font-size: var(--font-size-m);
}
.large {
font-size: var(--font-size-l);
}
.extraLarge {
font-size: var(--font-size-xl);
}
.white {
color: white;
}
.grey {
color: var(--grey-6);
}
.black {
color: var(--ink);
} }
</style> </style>

View file

@ -1,6 +1,6 @@
<script> <script>
import "@spectrum-css/checkbox/dist/index-vars.css" import Checkbox from "../Form/Checkbox.svelte"
import "@spectrum-css/actionbutton/dist/index-vars.css" import ActionButton from "../ActionButton/ActionButton.svelte"
export let selected export let selected
export let onToggleSelection export let onToggleSelection
@ -10,39 +10,8 @@
</script> </script>
{#if allowSelectRows} {#if allowSelectRows}
<label <Checkbox value={selected} />
class="spectrum-Checkbox spectrum-Checkbox--sizeM spectrum-Checkbox--emphasized">
<input
type="checkbox"
class="spectrum-Checkbox-input"
id="checkbox-1"
bind:checked={selected} />
<span class="spectrum-Checkbox-box">
<svg
class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Checkbox-checkmark"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Checkmark100" />
</svg>
<svg
class="spectrum-Icon spectrum-UIIcon-Dash100 spectrum-Checkbox-partialCheckmark"
focusable="false"
aria-hidden="true">
<use xlink:href="#spectrum-css-icon-Dash100" />
</svg>
</span>
</label>
{/if} {/if}
{#if allowEditRows} {#if allowEditRows}
<button <ActionButton size="s" on:click={onEdit}>Edit</ActionButton>
class="spectrum-ActionButton spectrum-ActionButton--sizeS"
on:click={onEdit}>
<span class="spectrum-ActionButton-label">Edit</span>
</button>
{/if} {/if}
<style>
label {
margin-right: 5px;
}
</style>

View file

@ -8,7 +8,7 @@
export let schema = {} export let schema = {}
export let showAutoColumns = false export let showAutoColumns = false
export let rowCount = 0 export let rowCount = 0
export let quiet = true export let quiet = false
export let loading = false export let loading = false
export let allowSelectRows = true export let allowSelectRows = true
export let allowEditRows = true export let allowEditRows = true
@ -70,10 +70,10 @@
} }
const getContentStyle = (visibleRows, rowCount) => { const getContentStyle = (visibleRows, rowCount) => {
if (!rowCount) { if (!rowCount || !visibleRows) {
return "" return ""
} }
return `height: ${headerHeight - 1 + visibleRows * (rowHeight + 1)}px;` return `height: ${headerHeight + visibleRows * (rowHeight + 1)}px;`
} }
const sortRows = (rows, sortColumn, sortOrder) => { const sortRows = (rows, sortColumn, sortOrder) => {
@ -266,7 +266,9 @@
</td> </td>
{/if} {/if}
{#each fields as field} {#each fields as field}
<td class="spectrum-Table-cell"> <td
class="spectrum-Table-cell"
class:spectrum-Table-cell--divider={!!schema[field].divider}>
<div class="spectrum-Table-cell-content"> <div class="spectrum-Table-cell-content">
<CellRenderer <CellRenderer
{customRenderers} {customRenderers}
@ -301,7 +303,7 @@
<style> <style>
.wrapper { .wrapper {
background-color: var(--spectrum-alias-background-color-primary); background-color: var(--spectrum-alias-background-color-default);
overflow: hidden; overflow: hidden;
position: relative; position: relative;
z-index: 1; z-index: 1;
@ -311,26 +313,23 @@
height: 100%; height: 100%;
position: relative; position: relative;
overflow: auto; overflow: auto;
border: 1px solid
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: var(--spectrum-global-color-gray-400) scrollbar-color: var(--spectrum-global-color-gray-400)
var(--spectrum-alias-background-color-primary); var(--spectrum-alias-background-color-default);
} }
.container::-webkit-scrollbar { .container::-webkit-scrollbar {
width: 16px; width: 10px;
height: 16px; height: 10px;
} }
.container::-webkit-scrollbar-track { .container::-webkit-scrollbar-track {
background: var(--spectrum-alias-background-color-primary); background: var(--spectrum-alias-background-color-default);
} }
.container::-webkit-scrollbar-thumb { .container::-webkit-scrollbar-thumb {
background-color: var(--spectrum-global-color-gray-400); background-color: var(--spectrum-global-color-gray-400);
border-radius: 20px; border-radius: 4px;
border: 4px solid var(--spectrum-alias-background-color-primary);
} }
.container::-webkit-scrollbar-corner { .container::-webkit-scrollbar-corner {
background: var(--spectrum-alias-background-color-primary); background: var(--spectrum-alias-background-color-default);
} }
.container.quiet { .container.quiet {
border: none !important; border: none !important;
@ -359,18 +358,15 @@
transition: opacity 0.2s ease; transition: opacity 0.2s ease;
} }
.container,
th {
border-bottom: 1px solid
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
}
th { th {
vertical-align: middle; vertical-align: middle;
height: var(--header-height); height: var(--header-height);
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 2; z-index: 2;
background-color: var(--spectrum-alias-background-color-primary); background-color: var(--spectrum-alias-background-color-default);
border-bottom: 1px solid
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
} }
.spectrum-Table-headCell-content { .spectrum-Table-headCell-content {
white-space: nowrap; white-space: nowrap;
@ -421,19 +417,20 @@
padding-top: 0; padding-top: 0;
padding-bottom: 0; padding-bottom: 0;
border-bottom: none !important; border-bottom: none !important;
border-left: none !important;
border-right: none !important;
border-top: 1px solid border-top: 1px solid
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important; var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
border-radius: 0 !important;
} }
tr:first-child td { tr:first-child td {
border-top: none !important; border-top: none !important;
} }
.container:not(.quiet) td.spectrum-Table-cell--divider { tr:last-child td {
width: 1px; border-bottom: 1px solid
border-right: 1px solid
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important; var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
} }
td.spectrum-Table-cell--divider {
width: 1px;
}
.spectrum-Table-cell-content { .spectrum-Table-cell-content {
height: var(--row-height); height: var(--row-height);
white-space: nowrap; white-space: nowrap;

View file

@ -1,78 +1,28 @@
<script> <script>
import "@spectrum-css/typography/dist/index-vars.css" import "@spectrum-css/typography/dist/index-vars.css"
// Level // Sizes
export let h1 = false; export let xxxl = false
export let h2 = false; export let xxl = false
export let h3 = false; export let xl = false
export let h4 = false; export let l = false
export let m = false
// Sizes export let s = false
export let xxxl = false; export let xs = false
export let xxl = false; export let xxs = false
export let xl = false;
export let l = false;
export let m = false;
export let s = false;
export let xs = false;
export let xxs = false;
export let serif = false;
$: useDefault = ![xxxl, xxl, xl, l, m, s, xs, xxs].includes(true)
</script> </script>
{#if h1} <h1
<h1 class="spectrum-Heading" class="spectrum-Heading"
class:spectrum-Heading--serif={serif} class:spectrum-Heading--sizeXXXL={xxxl}
class:spectrum-Heading--sizeXXXL={xxxl} class:spectrum-Heading--sizeXXL={xxl}
class:spectrum-Heading--sizeXXL={xxl} class:spectrum-Heading--sizeXL={xl}
class:spectrum-Heading--sizeXL={xl} class:spectrum-Heading--sizeL={l}
class:spectrum-Heading--sizeL={l} class:spectrum-Heading--sizeM={m || useDefault}
class:spectrum-Heading--sizeM={m} class:spectrum-Heading--sizeS={s}
class:spectrum-Heading--sizeS={s} class:spectrum-Heading--sizeXS={xs}
class:spectrum-Heading--sizeXS={xs} class:spectrum-Heading--sizeXXS={xxs}>
class:spectrum-Heading--sizeXXS={xxs}> <slot />
<slot /> </h1>
</h1>
{:else if h2}
<h2 class="spectrum-Heading"
class:spectrum-Heading--serif={serif}
class:spectrum-Heading--sizeXXXL={xxxl}
class:spectrum-Heading--sizeXXL={xxl}
class:spectrum-Heading--sizeXL={xl}
class:spectrum-Heading--sizeL={l}
class:spectrum-Heading--sizeM={m}
class:spectrum-Heading--sizeS={s}
class:spectrum-Heading--sizeXS={xs}
class:spectrum-Heading--sizeXXS={xxs}>
<slot />
</h2>
{:else if h3}
<h3 class="spectrum-Heading"
class:spectrum-Heading--serif={serif}
class:spectrum-Heading--sizeXXXL={xxxl}
class:spectrum-Heading--sizeXXL={xxl}
class:spectrum-Heading--sizeXL={xl}
class:spectrum-Heading--sizeL={l}
class:spectrum-Heading--sizeM={m}
class:spectrum-Heading--sizeS={s}
class:spectrum-Heading--sizeXS={xs}
class:spectrum-Heading--sizeXXS={xxs}>
<slot />
</h3>
{:else if h4}
<h4 class="spectrum-Heading"
class:spectrum-Heading--serif={serif}
class:spectrum-Heading--sizeXXXL={xxxl}
class:spectrum-Heading--sizeXXL={xxl}
class:spectrum-Heading--sizeXL={xl}
class:spectrum-Heading--sizeL={l}
class:spectrum-Heading--sizeM={m}
class:spectrum-Heading--sizeS={s}
class:spectrum-Heading--sizeXS={xs}
class:spectrum-Heading--sizeXXS={xxs}>
<slot />
</h4>
{:else}
SPECIFY HEADING SIZE!
{/if}

View file

@ -1,11 +1,13 @@
import "./bbui.css" import "./bbui.css"
// Spectrum icons
import "@spectrum-css/icon/dist/index-vars.css"
// Components // Components
export { default as Input } from "./Form/Input.svelte" export { default as Input } from "./Form/Input.svelte"
export { default as TextArea } from "./Form/TextArea.svelte" export { default as TextArea } from "./Form/TextArea.svelte"
export { default as RichText } from "./Form/RichText.svelte"
export { default as Select } from "./Form/Select.svelte" export { default as Select } from "./Form/Select.svelte"
export { default as DataList } from "./Form/DataList.svelte" export { default as Combobox } from "./Form/Combobox.svelte"
export { default as Dropzone } from "./Dropzone/Dropzone.svelte" export { default as Dropzone } from "./Dropzone/Dropzone.svelte"
export { default as Drawer } from "./Drawer/Drawer.svelte" export { default as Drawer } from "./Drawer/Drawer.svelte"
export { default as ActionButton } from "./ActionButton/ActionButton.svelte" export { default as ActionButton } from "./ActionButton/ActionButton.svelte"
@ -14,7 +16,7 @@ export { default as ActionMenu } from "./ActionMenu/ActionMenu.svelte"
export { default as Button } from "./Button/Button.svelte" export { default as Button } from "./Button/Button.svelte"
export { default as Icon, directions } from "./Icon/Icon.svelte" export { default as Icon, directions } from "./Icon/Icon.svelte"
export { default as Toggle } from "./Form/Toggle.svelte" export { default as Toggle } from "./Form/Toggle.svelte"
export { default as Radio } from "./Form/Radio.svelte" export { default as RadioGroup } from "./Form/RadioGroup.svelte"
export { default as Checkbox } from "./Form/Checkbox.svelte" export { default as Checkbox } from "./Form/Checkbox.svelte"
export { default as Home } from "./Links/Home.svelte" export { default as Home } from "./Links/Home.svelte"
export { default as DetailSummary } from "./List/Items/DetailSummary.svelte" export { default as DetailSummary } from "./List/Items/DetailSummary.svelte"
@ -34,9 +36,8 @@ export { default as Modal } from "./Modal/Modal.svelte"
export { default as ModalContent } from "./Modal/ModalContent.svelte" export { default as ModalContent } from "./Modal/ModalContent.svelte"
export { default as NotificationDisplay } from "./Notification/NotificationDisplay.svelte" export { default as NotificationDisplay } from "./Notification/NotificationDisplay.svelte"
export { default as Spacer } from "./Spacer/Spacer.svelte" export { default as Spacer } from "./Spacer/Spacer.svelte"
export { default as DatePicker } from "./DatePicker/DatePicker.svelte" export { default as DatePicker } from "./Form/DatePicker.svelte"
export { default as Multiselect } from "./Form/Multiselect.svelte" export { default as Multiselect } from "./Form/Multiselect.svelte"
export { default as Slider } from "./Form/Slider.svelte"
export { default as Context } from "./context" export { default as Context } from "./context"
export { default as Table } from "./Table/Table.svelte" export { default as Table } from "./Table/Table.svelte"
export { default as Tabs } from "./Tabs/Tabs.svelte" export { default as Tabs } from "./Tabs/Tabs.svelte"
@ -44,6 +45,7 @@ export { default as Tab } from "./Tabs/Tab.svelte"
export { default as TreeView } from "./TreeView/Tree.svelte" export { default as TreeView } from "./TreeView/Tree.svelte"
export { default as TreeItem } from "./TreeView/Item.svelte" export { default as TreeItem } from "./TreeView/Item.svelte"
export { default as Divider } from "./Divider/Divider.svelte" export { default as Divider } from "./Divider/Divider.svelte"
export { default as Search } from "./Form/Search.svelte"
// Typography // Typography
export { default as Body } from "./Typography/Body.svelte" export { default as Body } from "./Typography/Body.svelte"
@ -51,6 +53,8 @@ export { default as Heading } from "./Typography/Heading.svelte"
export { default as Detail } from "./Typography/Detail.svelte" export { default as Detail } from "./Typography/Detail.svelte"
export { default as Code } from "./Typography/Code.svelte" export { default as Code } from "./Typography/Code.svelte"
// Core form components to be used elsewhere (standard components)
export * from "./Form/Core"
// Actions // Actions
export { default as autoResizeTextArea } from "./Actions/autoresize_textarea" export { default as autoResizeTextArea } from "./Actions/autoresize_textarea"

View file

@ -1,8 +1,7 @@
import "@spectrum-css/icon/dist/index-vars.css"
import SpectrumUIIcons from "@spectrum-css/icon/dist/spectrum-css-icons.svg" import SpectrumUIIcons from "@spectrum-css/icon/dist/spectrum-css-icons.svg"
import SpectrumWorkflowIcons from "@adobe/spectrum-css-workflow-icons/dist/spectrum-icons.svg" import SpectrumWorkflowIcons from "@adobe/spectrum-css-workflow-icons/dist/spectrum-icons.svg"
export const loadSpectrumIcons = () => { export default () => {
loadIconSet("Spectrum UI Icons", SpectrumUIIcons) loadIconSet("Spectrum UI Icons", SpectrumUIIcons)
loadIconSet("Spectrum Workflow Icons", SpectrumWorkflowIcons) loadIconSet("Spectrum Workflow Icons", SpectrumWorkflowIcons)
} }

View file

@ -1,8 +1,7 @@
import "@spectrum-css/icon/dist/index-vars.css"
import SpectrumUIIcons from "@spectrum-css/icon/dist/spectrum-css-icons.svg?raw" import SpectrumUIIcons from "@spectrum-css/icon/dist/spectrum-css-icons.svg?raw"
import SpectrumWorkflowIcons from "@adobe/spectrum-css-workflow-icons/dist/spectrum-icons.svg?raw" import SpectrumWorkflowIcons from "@adobe/spectrum-css-workflow-icons/dist/spectrum-icons.svg?raw"
export const loadSpectrumIcons = () => { export default () => {
loadIconSet("Spectrum UI Icons", SpectrumUIIcons) loadIconSet("Spectrum UI Icons", SpectrumUIIcons)
loadIconSet("Spectrum Workflow Icons", SpectrumWorkflowIcons) loadIconSet("Spectrum Workflow Icons", SpectrumWorkflowIcons)
} }

View file

@ -1,14 +0,0 @@
export default function buildStyle(styles) {
const convertCamel = str => {
return str.replace(/[A-Z]/g, match => `-${match.toLowerCase()}`)
}
let str = ""
for (let s in styles) {
if (styles[s]) {
let key = convertCamel(s)
str += `${key}: ${styles[s]}; `
}
}
return str
}

View file

@ -0,0 +1,8 @@
export const generateID = () => {
const rand = Math.random()
.toString(32)
.substring(2)
// Starts with a letter so that its a valid DOM ID
return `A${rand}`
}

View file

@ -1,22 +0,0 @@
import svelte from "@sveltejs/vite-plugin-svelte"
export default ({ mode }) => {
const isProduction = mode === "production"
return {
build: {
lib: {
entry: "src/index.js",
name: "bbui",
formats: ["es"],
},
minify: isProduction,
},
plugins: [svelte()],
resolve: {
dedupe: ["svelte", "svelte/internal"],
},
rollupOptions: {
external: ["svelte", "svelte/internal"],
},
}
}

File diff suppressed because it is too large Load diff

View file

@ -64,17 +64,13 @@
} }
}, },
"dependencies": { "dependencies": {
"@adobe/spectrum-css-workflow-icons": "^1.2.0",
"@budibase/bbui": "^1.58.13", "@budibase/bbui": "^1.58.13",
"@budibase/client": "^0.8.9", "@budibase/client": "^0.8.9",
"@budibase/colorpicker": "1.1.2", "@budibase/colorpicker": "1.1.2",
"@budibase/string-templates": "^0.8.9", "@budibase/string-templates": "^0.8.9",
"@budibase/svelte-ag-grid": "^1.0.4",
"@sentry/browser": "5.19.1", "@sentry/browser": "5.19.1",
"@spectrum-css/icon": "^3.0.1",
"@spectrum-css/page": "^3.0.1", "@spectrum-css/page": "^3.0.1",
"@spectrum-css/vars": "^3.0.1", "@spectrum-css/vars": "^3.0.1",
"@svelteschool/svelte-forms": "0.7.0",
"codemirror": "^5.59.0", "codemirror": "^5.59.0",
"downloadjs": "1.4.7", "downloadjs": "1.4.7",
"lodash": "4.17.13", "lodash": "4.17.13",

View file

@ -87,7 +87,7 @@ const createScreen = table => {
dataProvider: `{{ literal ${makePropSafe(provider._json._id)} }}`, dataProvider: `{{ literal ${makePropSafe(provider._json._id)} }}`,
theme: "spectrum--lightest", theme: "spectrum--lightest",
showAutoColumns: false, showAutoColumns: false,
quiet: false, quiet: true,
size: "spectrum--medium", size: "spectrum--medium",
rowCount: 8, rowCount: 8,
}) })

View file

@ -5,19 +5,8 @@ export const getThemeStore = () => {
const initialValue = { const initialValue = {
darkMode: true, darkMode: true,
} }
const store = localStorageStore("bb-theme", initialValue) const store = localStorageStore("bb-theme", initialValue)
// Resets the custom theme to the default dark theme.
// The reset option is only available when dark theme is on, which is why it
// sets dark mode to true here
store.reset = () => {
store.set({
...initialValue,
darkMode: true,
})
}
// Update theme when store changes // Update theme when store changes
store.subscribe(theme => { store.subscribe(theme => {
themeElement.classList.toggle("spectrum--darkest", theme.darkMode) themeElement.classList.toggle("spectrum--darkest", theme.darkMode)

View file

@ -25,6 +25,7 @@
<ModalContent <ModalContent
title="Create Automation" title="Create Automation"
confirmText="Create" confirmText="Create"
size="L"
onConfirm={createAutomation} onConfirm={createAutomation}
disabled={!valid}> disabled={!valid}>
<Input bind:value={name} label="Name" /> <Input bind:value={name} label="Name" />

View file

@ -11,7 +11,7 @@
{#if error} {#if error}
<div class="errors">{error}</div> <div class="errors">{error}</div>
{/if} {/if}
<Table title={''} schema={query.schema} {data} {loading} /> <Table schema={query.schema} {data} {loading} rowCount={5} />
<style> <style>
.errors { .errors {

View file

@ -5,7 +5,7 @@
Label, Label,
DatePicker, DatePicker,
Toggle, Toggle,
RichText, TextArea,
} from "@budibase/bbui" } from "@budibase/bbui"
import Dropzone from "components/common/Dropzone.svelte" import Dropzone from "components/common/Dropzone.svelte"
import { capitalise } from "../../../helpers" import { capitalise } from "../../../helpers"
@ -21,12 +21,11 @@
</script> </script>
{#if type === 'options'} {#if type === 'options'}
<Select thin secondary {label} data-cy="{meta.name}-select" bind:value> <Select
<option value="">Choose an option</option> {label}
{#each meta.constraints.inclusion as opt} data-cy="{meta.name}-select"
<option value={opt}>{opt}</option> bind:value
{/each} options={meta.constraints.inclusion} />
</Select>
{:else if type === 'datetime'} {:else if type === 'datetime'}
<DatePicker {label} bind:value /> <DatePicker {label} bind:value />
{:else if type === 'attachment'} {:else if type === 'attachment'}
@ -41,13 +40,9 @@
<LinkedRowSelector bind:linkedRows={value} schema={meta} /> <LinkedRowSelector bind:linkedRows={value} schema={meta} />
</div> </div>
{:else if type === 'longform'} {:else if type === 'longform'}
<div> <TextArea {label} bind:value />
<Label extraSmall grey>{label}</Label>
<RichText bind:value />
</div>
{:else} {:else}
<Input <Input
thin
{label} {label}
data-cy="{meta.name}-input" data-cy="{meta.name}-input"
{type} {type}

View file

@ -10,7 +10,6 @@
import CreateEditRow from "./modals/CreateEditRow.svelte" import CreateEditRow from "./modals/CreateEditRow.svelte"
import CreateEditUser from "./modals/CreateEditUser.svelte" import CreateEditUser from "./modals/CreateEditUser.svelte"
import CreateEditColumn from "./modals/CreateEditColumn.svelte" import CreateEditColumn from "./modals/CreateEditColumn.svelte"
import "@budibase/svelte-ag-grid/dist/index.css"
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants" import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
import RoleCell from "./cells/RoleCell.svelte" import RoleCell from "./cells/RoleCell.svelte"
@ -22,6 +21,7 @@
export let loading = false export let loading = false
export let theme = "alpine" export let theme = "alpine"
export let hideAutocolumns export let hideAutocolumns
export let rowCount
let selectedRows = [] let selectedRows = []
let editableColumn let editableColumn
@ -91,7 +91,9 @@
<div> <div>
<div class="table-title"> <div class="table-title">
<h1>{title}</h1> {#if title}
<h1>{title}</h1>
{/if}
{#if loading} {#if loading}
<div transition:fade> <div transition:fade>
<Spinner size="10" /> <Spinner size="10" />
@ -111,6 +113,7 @@
{schema} {schema}
{loading} {loading}
{customRenderers} {customRenderers}
{rowCount}
bind:selectedRows bind:selectedRows
allowSelectRows={allowEditing} allowSelectRows={allowEditing}
allowEditRows={allowEditing} allowEditRows={allowEditing}

View file

@ -1,171 +0,0 @@
<script>
import { onMount, onDestroy } from "svelte"
import { Modal, ModalContent } from "@budibase/bbui"
import CreateEditColumn from "../modals/CreateEditColumn.svelte"
import { FIELDS } from "constants/backend"
const SORT_ICON_MAP = {
asc: "ri-arrow-down-fill",
desc: "ri-arrow-up-fill",
}
export let field
export let displayName
export let column
export let enableSorting = true
export let showColumnMenu
export let progressSort
export let editable
let menuButton
let sortDirection = ""
let modal
let hovered
let filterActive
function toggleMenu() {
showColumnMenu(menuButton)
}
function onSort(event) {
progressSort(event.shiftKey)
}
function showModal() {
modal.show()
}
function setSort() {
sortDirection = column.getSort()
}
function setFilterActive(e) {
filterActive = e.column.filterActive
}
onMount(() => {
column.addEventListener("sortChanged", setSort)
column.addEventListener("filterActiveChanged", setFilterActive)
})
onDestroy(() => {
column.removeEventListener("sortChanged", setSort)
column.removeEventListener("filterActiveChanged", setFilterActive)
})
$: type = FIELDS[field?.type?.toUpperCase()]?.name
</script>
<header
on:click={onSort}
data-cy="table-header"
on:mouseover={() => (hovered = true)}
on:mouseleave={() => (hovered = false)}>
<div class="column-header">
<div class="column-header-text">
<div class="column-header-name">
{displayName}
{#if field.autocolumn}<i class="auto ri-magic-fill" />{/if}
</div>
{#if type}
<div class="column-header-type">{type}</div>
{/if}
</div>
<i class={`${SORT_ICON_MAP[sortDirection]} icon`} />
</div>
<Modal bind:this={modal}>
<ModalContent
size="large"
showCancelButton={false}
showConfirmButton={false}
title={`Edit Column: ${field.name}`}>
<CreateEditColumn onClosed={modal.hide} {field} />
</ModalContent>
</Modal>
<section class:show={hovered || filterActive}>
{#if editable && hovered}
<span on:click|stopPropagation={showModal}>
<i class="ri-pencil-line icon" />
</span>
{/if}
<span on:click|stopPropagation={toggleMenu} bind:this={menuButton}>
<i class="ri-filter-line icon" class:active={filterActive} />
</span>
</section>
</header>
<style>
header {
font-family: Inter;
font-weight: 600;
display: flex;
justify-content: space-between;
height: 100%;
width: 100%;
align-items: center;
color: var(--ink);
}
section {
opacity: 0;
transition: 0.3s all;
}
section.show {
opacity: 1;
}
.column-header {
display: flex;
flex-direction: row;
justify-content: flex-start;
align-items: center;
gap: var(--spacing-s);
}
.column-header-text {
flex: 1 1 auto;
display: flex;
flex-direction: column;
justify-content: flex-start;
align-items: stretch;
gap: var(--spacing-xs);
}
.column-header-name {
white-space: normal !important;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 1;
overflow: hidden;
}
.column-header-type {
font-size: var(--font-size-xs);
color: var(--grey-6);
}
.icon {
transition: 0.2s all;
font-size: var(--font-size-m);
font-weight: 500;
}
.auto {
font-size: 9px;
transition: none;
position: relative;
margin-left: 2px;
top: -3px;
color: var(--grey-6);
}
.icon:hover {
color: var(--blue);
}
.icon.active,
.icon:hover {
color: var(--blue);
}
</style>

View file

@ -1,35 +0,0 @@
import TableHeader from "./TableHeader.svelte"
export default class TableHeaderWrapper {
constructor() {}
init(params) {
this.agParams = params
this.container = document.createElement("div")
this.container.style.height = "100%"
this.container.style.width = "100%"
this.headerComponent = new TableHeader({
target: this.container,
props: params,
})
this.gui = this.container
}
// can get called more than once, you should return the HTML element
getGui() {
return this.gui
}
// gets called when a new Column Definition has been set for this header
refresh(params) {
this.agParams = params
this.headerComponent = new TableHeader({
target: this.container,
props: params,
})
}
// optional method, gets called once, when component is destroyed
destroy() {}
}

View file

@ -1,27 +0,0 @@
<script>
import Spinner from "components/common/Spinner.svelte"
import { fade } from "svelte/transition"
import Logo from "/assets/bb-logo.svg"
</script>
<div class="ag-overlay-loading-center loading-container">
<div transition:fade class="loading-overlay">
<img height="30" width="30" src={Logo} alt="Budibase icon" />
<span> Loading Your Data </span>
<Spinner size="12" />
</div>
</div>
<style>
.loading-overlay {
display: flex;
align-items: center;
font-family: var(--font-sans);
font-weight: 500;
color: var(--ink);
}
.loading-overlay > * {
margin-right: var(--spacing-m);
}
</style>

View file

@ -1,17 +0,0 @@
import LoadingOverlay from "./LoadingOverlay.svelte"
export default class LoadingOverlayWrapper {
init(params) {
this.gui = document.createElement("div")
new LoadingOverlay({
target: this.gui,
props: {
params,
},
})
}
getGui() {
return this.gui
}
}

View file

@ -1,10 +1,5 @@
<script> <script>
import { import { Button, Icon, Modal, ModalContent } from "@budibase/bbui"
Button,
Icon,
Modal,
ModalContent,
} from "@budibase/bbui"
import CreateEditColumn from "../modals/CreateEditColumn.svelte" import CreateEditColumn from "../modals/CreateEditColumn.svelte"
let modal let modal
@ -14,11 +9,5 @@
Create New Column Create New Column
</Button> </Button>
<Modal bind:this={modal}> <Modal bind:this={modal}>
<ModalContent <CreateEditColumn />
size="large"
showCancelButton={false}
showConfirmButton={false}
title={'Create Column'}>
<CreateEditColumn onClosed={modal.hide} />
</ModalContent>
</Modal> </Modal>

View file

@ -5,11 +5,13 @@
Label, Label,
Select, Select,
Toggle, Toggle,
Radio, RadioGroup,
DatePicker,
ModalContent,
Context,
} from "@budibase/bbui" } from "@budibase/bbui"
import { cloneDeep } from "lodash/fp" import { cloneDeep } from "lodash/fp"
import { tables } from "stores/backend" import { tables } from "stores/backend"
import { TableNames, UNEDITABLE_USER_FIELDS } from "constants" import { TableNames, UNEDITABLE_USER_FIELDS } from "constants"
import { import {
FIELDS, FIELDS,
@ -19,15 +21,15 @@
import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils" import { getAutoColumnInformation, buildAutoColumn } from "builderStore/utils"
import { notifications } from "@budibase/bbui" import { notifications } from "@budibase/bbui"
import ValuesList from "components/common/ValuesList.svelte" import ValuesList from "components/common/ValuesList.svelte"
import DatePicker from "components/common/DatePicker.svelte"
import ConfirmDialog from "components/common/ConfirmDialog.svelte" import ConfirmDialog from "components/common/ConfirmDialog.svelte"
import { truncate } from "lodash" import { truncate } from "lodash"
import { getContext } from "svelte"
const AUTO_COL = "auto" const AUTO_COL = "auto"
const LINK_TYPE = FIELDS.LINK.type const LINK_TYPE = FIELDS.LINK.type
let fieldDefinitions = cloneDeep(FIELDS) let fieldDefinitions = cloneDeep(FIELDS)
const { hide } = getContext(Context.Modal)
export let onClosed
export let field = { export let field = {
type: "string", type: "string",
constraints: fieldDefinitions.STRING.constraints, constraints: fieldDefinitions.STRING.constraints,
@ -54,8 +56,11 @@
$tables.selected?._id === TableNames.USERS && $tables.selected?._id === TableNames.USERS &&
UNEDITABLE_USER_FIELDS.includes(field.name) UNEDITABLE_USER_FIELDS.includes(field.name)
$: invalid = $: invalid =
!field.name ||
(field.type === LINK_TYPE && !field.tableId) || (field.type === LINK_TYPE && !field.tableId) ||
Object.keys($tables.draft.schema).some(key => key === field.name) Object.keys($tables.draft?.schema ?? {}).some(
key => key !== originalName && key === field.name
)
// used to select what different options can be displayed for column type // used to select what different options can be displayed for column type
$: canBeSearched = $: canBeSearched =
@ -77,7 +82,6 @@
primaryDisplay, primaryDisplay,
indexes, indexes,
}) })
onClosed()
} }
function deleteColumn() { function deleteColumn() {
@ -86,37 +90,37 @@
} else { } else {
tables.deleteField(field) tables.deleteField(field)
notifications.success(`Column ${field.name} deleted.`) notifications.success(`Column ${field.name} deleted.`)
onClosed() hide()
} }
} }
function handleTypeChange(event) { function handleTypeChange(event) {
const definition = fieldDefinitions[event.target.value.toUpperCase()]
if (!definition) {
return
}
// remove any extra fields that may not be related to this type // remove any extra fields that may not be related to this type
delete field.autocolumn delete field.autocolumn
delete field.subtype delete field.subtype
delete field.tableId delete field.tableId
delete field.relationshipType delete field.relationshipType
// add in defaults and initial definition
field.type = definition.type // Add in defaults and initial definition
field.constraints = definition.constraints const definition = fieldDefinitions[event.detail?.toUpperCase()]
// default relationships many to many if (definition?.constraints) {
field.constraints = definition.constraints
}
// Default relationships many to many
if (field.type === LINK_TYPE) { if (field.type === LINK_TYPE) {
field.relationshipType = RelationshipTypes.MANY_TO_MANY field.relationshipType = RelationshipTypes.MANY_TO_MANY
} }
} }
function onChangeRequired(e) { function onChangeRequired(e) {
const req = e.target.checked const req = e.detail
field.constraints.presence = req ? { allowEmpty: false } : false field.constraints.presence = req ? { allowEmpty: false } : false
required = req required = req
} }
function onChangePrimaryDisplay(e) { function onChangePrimaryDisplay(e) {
const isPrimary = e.target.checked const isPrimary = e.detail
// primary display is always required // primary display is always required
if (isPrimary) { if (isPrimary) {
field.constraints.presence = { allowEmpty: false } field.constraints.presence = { allowEmpty: false }
@ -124,11 +128,11 @@
} }
function onChangePrimaryIndex(e) { function onChangePrimaryIndex(e) {
indexes = e.target.checked ? [field.name] : [] indexes = e.detail ? [field.name] : []
} }
function onChangeSecondaryIndex(e) { function onChangeSecondaryIndex(e) {
if (e.target.checked) { if (e.detail) {
indexes[1] = field.name indexes[1] = field.name
} else { } else {
indexes = indexes.slice(0, 1) indexes = indexes.slice(0, 1)
@ -172,58 +176,60 @@
} }
</script> </script>
<div class="actions" class:hidden={deletion}> <ModalContent
<Input label="Name" thin bind:value={field.name} disabled={uneditable} /> title={originalName ? 'Edit Column' : 'Create Column'}
confirmText="Save Column"
onConfirm={saveColumn}
disabled={invalid}>
<Input label="Name" bind:value={field.name} disabled={uneditable} />
<Select <Select
disabled={originalName} disabled={originalName}
secondary
thin
label="Type" label="Type"
bind:value={field.type}
on:change={handleTypeChange} on:change={handleTypeChange}
bind:value={field.type}> options={[...Object.values(fieldDefinitions), { name: 'Auto Column', type: AUTO_COL }]}
{#each Object.values(fieldDefinitions) as field} getOptionLabel={field => field.name}
<option value={field.type}>{field.name}</option> getOptionValue={field => field.type} />
{/each}
<option value={AUTO_COL}>Auto Column</option>
</Select>
{#if canBeRequired} {#if canBeRequired || canBeDisplay}
<Toggle <div>
checked={required} {#if canBeRequired}
on:change={onChangeRequired} <Toggle
disabled={primaryDisplay} value={required}
thin on:change={onChangeRequired}
text="Required" /> disabled={primaryDisplay}
thin
text="Required" />
{/if}
{#if canBeDisplay}
<Toggle
bind:value={primaryDisplay}
on:change={onChangePrimaryDisplay}
thin
text="Use as table display column" />
{/if}
</div>
{/if} {/if}
{#if canBeDisplay}
<Toggle
bind:checked={primaryDisplay}
on:change={onChangePrimaryDisplay}
thin
text="Use as table display column" />
<Label grey small>Search Indexes</Label>
{/if}
{#if canBeSearched} {#if canBeSearched}
<Toggle <div>
checked={indexes[0] === field.name} <Label grey small>Search Indexes</Label>
disabled={indexes[1] === field.name} <Toggle
on:change={onChangePrimaryIndex} value={indexes[0] === field.name}
thin disabled={indexes[1] === field.name}
text="Primary" /> on:change={onChangePrimaryIndex}
<Toggle text="Primary" />
checked={indexes[1] === field.name} <Toggle
disabled={!indexes[0] || indexes[0] === field.name} value={indexes[1] === field.name}
on:change={onChangeSecondaryIndex} disabled={!indexes[0] || indexes[0] === field.name}
thin on:change={onChangeSecondaryIndex}
text="Secondary" /> text="Secondary" />
</div>
{/if} {/if}
{#if field.type === 'string'} {#if field.type === 'string'}
<Input <Input
thin
type="number" type="number"
label="Max Length" label="Max Length"
bind:value={field.constraints.length.maximum} /> bind:value={field.constraints.length.maximum} />
@ -238,64 +244,46 @@
<DatePicker label="Latest" bind:value={field.constraints.datetime.latest} /> <DatePicker label="Latest" bind:value={field.constraints.datetime.latest} />
{:else if field.type === 'number'} {:else if field.type === 'number'}
<Input <Input
thin
type="number" type="number"
label="Min Value" label="Min Value"
bind:value={field.constraints.numericality.greaterThanOrEqualTo} /> bind:value={field.constraints.numericality.greaterThanOrEqualTo} />
<Input <Input
thin
type="number" type="number"
label="Max Value" label="Max Value"
bind:value={field.constraints.numericality.lessThanOrEqualTo} /> bind:value={field.constraints.numericality.lessThanOrEqualTo} />
{:else if field.type === 'link'} {:else if field.type === 'link'}
<Select label="Table" thin secondary bind:value={field.tableId}> <Select
<option value="">Choose an option</option> label="Table"
{#each tableOptions as table} bind:value={field.tableId}
<option value={table._id}>{table.name}</option> options={tableOptions}
{/each} getOptionLabel={table => table.name}
</Select> getOptionValue={table => table._id} />
{#if relationshipOptions && relationshipOptions.length > 0} {#if relationshipOptions && relationshipOptions.length > 0}
<div> <RadioGroup
<Label grey extraSmall>Define the relationship</Label> disabled={originalName}
<div class="radio-buttons"> label="Define the relationship"
{#each relationshipOptions as { value, name }} bind:value={field.relationshipType}
<Radio options={relationshipOptions}
disabled={originalName} getOptionLabel={option => option.name}
name="Relationship type" getOptionValue={option => option.value} />
{value}
bind:group={field.relationshipType}>
<div class="radio-button-labels">
<label for={value}>{name.split('→')[0]}</label>
<label class="rel-type-center" for={value}>→</label>
<label for={value}>{name.split('→')[1]}</label>
</div>
</Radio>
{/each}
</div>
</div>
{/if} {/if}
<Input <Input label={`Column name in other table`} bind:value={field.fieldName} />
label={`Column Name in Other Table`}
thin
bind:value={field.fieldName} />
{:else if field.type === AUTO_COL} {:else if field.type === AUTO_COL}
<Select label="Auto Column Type" thin secondary bind:value={field.subtype}> <Select
<option value="">Choose a subtype</option> label="Auto Column Type"
{#each Object.entries(getAutoColumnInformation()) as [subtype, info]} value={field.subtype}
<option value={subtype}>{info.name}</option> on:change={e => (field.subtype = e.detail)}
{/each} options={Object.entries(getAutoColumnInformation())}
</Select> getOptionLabel={option => option[1].name}
getOptionValue={option => option[0]} />
{/if} {/if}
<footer class="create-column-options">
<div slot="footer">
{#if !uneditable && originalName != null} {#if !uneditable && originalName != null}
<Button warning size="S" text on:click={confirmDelete}>Delete Column</Button> <Button warning text on:click={confirmDelete}>Delete</Button>
{/if} {/if}
<Button on:click={onClosed}>Cancel</Button> </div>
<Button cta on:click={saveColumn} bind:disabled={invalid}> </ModalContent>
Save Column
</Button>
</footer>
</div>
<ConfirmDialog <ConfirmDialog
bind:this={confirmDeleteDialog} bind:this={confirmDeleteDialog}
body={`Are you sure you wish to delete this column? Your data will be deleted and this action cannot be undone.`} body={`Are you sure you wish to delete this column? Your data will be deleted and this action cannot be undone.`}
@ -303,44 +291,3 @@
onOk={deleteColumn} onOk={deleteColumn}
onCancel={hideDeleteDialog} onCancel={hideDeleteDialog}
title="Confirm Deletion" /> title="Confirm Deletion" />
<style>
.radio-buttons {
gap: var(--spacing-m);
font-size: var(--font-size-xs);
}
.radio-buttons :global(> *) {
margin-top: var(--spacing-s);
width: 100%;
}
.actions {
display: grid;
grid-gap: var(--spacing-xl);
}
footer {
display: flex;
justify-content: flex-end;
gap: var(--spacing-m);
}
:global(.create-column-options button:first-child) {
margin-right: auto;
}
.rel-type-center {
font-weight: 500;
color: var(--grey-6);
margin-right: 4px;
margin-left: 4px;
padding: 1px 3px 1px 3px;
background: var(--grey-3);
border-radius: 2px;
}
.radio-button-labels {
margin-top: 2px;
}
</style>

View file

@ -17,6 +17,7 @@
: $tables.selected : $tables.selected
$: tableSchema = getUserSchema(table) $: tableSchema = getUserSchema(table)
$: customSchemaKeys = getCustomSchemaKeys(tableSchema) $: customSchemaKeys = getCustomSchemaKeys(tableSchema)
$: if (!row.status) row.status = "active"
const getUserSchema = table => { const getUserSchema = table => {
let schema = table?.schema ?? {} let schema = table?.schema ?? {}
@ -86,20 +87,18 @@
<!-- Defer rendering this select until roles load, otherwise the initial <!-- Defer rendering this select until roles load, otherwise the initial
selection is always undefined --> selection is always undefined -->
<Select <Select
thin
secondary
label="Role" label="Role"
data-cy="roleId-select" data-cy="roleId-select"
bind:value={row.roleId}> bind:value={row.roleId}
<option value="">Choose an option</option> options={$roles}
{#each $roles as role} getOptionLabel={role => role.name}
<option value={role._id}>{role.name}</option> getOptionValue={role => role._id} />
{/each} <Select
</Select> label="Status"
<RowFieldControl
meta={{ name: 'status', type: 'options', constraints: { inclusion: ['active', 'inactive'] } }}
bind:value={row.status} bind:value={row.status}
defaultValue={'active'} /> options={[{ label: 'Active', value: 'active' }, { label: 'Inactive', value: 'inactive' }]}
getOptionLabel={status => status.label}
getOptionValue={status => status.value} />
{#each customSchemaKeys as [key, meta]} {#each customSchemaKeys as [key, meta]}
{#if !meta.autocolumn} {#if !meta.autocolumn}
<RowFieldControl {meta} bind:value={row[key]} {creating} /> <RowFieldControl {meta} bind:value={row[key]} {creating} />

View file

@ -1,8 +1,6 @@
<script> <script>
import { Button, Select } from "@budibase/bbui" import { Button, Select, Label, notifications } from "@budibase/bbui"
import { tables, views } from "stores/backend" import { tables, views } from "stores/backend"
import { notifications } from "@budibase/bbui"
import analytics from "analytics" import analytics from "analytics"
const CALCULATIONS = [ const CALCULATIONS = [
@ -45,33 +43,29 @@
<div class="actions"> <div class="actions">
<h5>Calculate</h5> <h5>Calculate</h5>
<div class="input-group-row"> <div class="input-group-row">
<p>The</p> <Label>The</Label>
<Select secondary thin bind:value={view.calculation}> <Select
<option value={''}>Choose an option</option> bind:value={view.calculation}
{#each CALCULATIONS as calculation} options={CALCULATIONS}
<option value={calculation.key}>{calculation.name}</option> getOptionLabel={x => x.name}
{/each} getOptionValue={x => x.key} />
</Select>
{#if view.calculation} {#if view.calculation}
<p>of</p> <Label>Of</Label>
<Select secondary thin bind:value={view.field}> <Select bind:value={view.field} options={fields} />
<option value={''}>You must choose an option</option>
{#each fields as field}
<option value={field}>{field}</option>
{/each}
</Select>
{/if} {/if}
</div> </div>
<div class="footer"> <div class="footer">
<Button secondary on:click={onClosed}>Cancel</Button> <Button secondary on:click={onClosed}>Cancel</Button>
<Button primary on:click={saveView} disabled={!view.field}>Save</Button> <Button cta on:click={saveView} disabled={!view.field}>Save</Button>
</div> </div>
</div> </div>
<style> <style>
.actions { .actions {
width: 200px;
display: grid; display: grid;
grid-gap: var(--spacing-xl); grid-gap: var(--spacing-xl);
padding: var(--spacing-xl);
} }
h5 { h5 {
@ -91,9 +85,4 @@
gap: var(--spacing-s); gap: var(--spacing-s);
align-items: center; align-items: center;
} }
p {
margin: 0;
font-size: var(--font-size-xs);
}
</style> </style>

View file

@ -35,7 +35,7 @@
<Input label="View Name" thin bind:value={name} /> <Input label="View Name" thin bind:value={name} />
<div class="footer"> <div class="footer">
<Button secondary on:click={onClosed}>Cancel</Button> <Button secondary on:click={onClosed}>Cancel</Button>
<Button primary on:click={saveView}>Save View</Button> <Button cta on:click={saveView}>Save View</Button>
</div> </div>
</div> </div>
@ -48,6 +48,7 @@
.actions { .actions {
display: grid; display: grid;
grid-gap: var(--spacing-xl); grid-gap: var(--spacing-xl);
padding: var(--spacing-xl);
} }
.footer { .footer {

View file

@ -30,14 +30,15 @@
<div class="popover"> <div class="popover">
<h5>Export Data</h5> <h5>Export Data</h5>
<Select label="Format" secondary thin bind:value={exportFormat}> <Select
{#each FORMATS as format} label="Format"
<option value={format.key}>{format.name}</option> bind:value={exportFormat}
{/each} options={FORMATS}
</Select> getOptionLabel={x => x.name}
getOptionValue={x => x.key} />
<div class="footer"> <div class="footer">
<Button secondary on:click={onClosed}>Cancel</Button> <Button secondary on:click={onClosed}>Cancel</Button>
<Button primary on:click={exportView}>Export</Button> <Button cta on:click={exportView}>Export</Button>
</div> </div>
</div> </div>
@ -45,6 +46,7 @@
.popover { .popover {
display: grid; display: grid;
grid-gap: var(--spacing-xl); grid-gap: var(--spacing-xl);
padding: var(--spacing-xl);
} }
h5 { h5 {

View file

@ -105,6 +105,9 @@
filter.value = "" filter.value = ""
} }
} }
const getOptionLabel = x => x.name
const getOptionValue = x => x.key
</script> </script>
<div class="actions"> <div class="actions">
@ -115,49 +118,37 @@
{#if idx === 0} {#if idx === 0}
<p>Where</p> <p>Where</p>
{:else} {:else}
<Select secondary thin bind:value={filter.conjunction}> <Select
<option value="">Choose an option</option> bind:value={filter.conjunction}
{#each CONJUNCTIONS as conjunction} options={CONJUNCTIONS}
<option value={conjunction.key}>{conjunction.name}</option> {getOptionLabel}
{/each} {getOptionValue} />
</Select>
{/if} {/if}
<Select <Select
secondary
thin
bind:value={filter.key} bind:value={filter.key}
on:change={fieldChanged(filter)}> on:change={fieldChanged(filter)}
<option value="">Choose an option</option> options={fields} />
{#each fields as field} <Select
<option value={field}>{field}</option> bind:value={filter.condition}
{/each} options={CONDITIONS}
</Select> {getOptionLabel}
<Select secondary thin bind:value={filter.condition}> {getOptionValue} />
<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)} {#if filter.key && isMultipleChoice(filter.key)}
<Select secondary thin bind:value={filter.value}> <Select
<option value="">Choose an option</option> bind:value={filter.value}
{#each fieldOptions(filter.key) as option} options={fieldOptions(filter.key)}
<option value={option}>{option.toString()}</option> getOptionLabel={x => x.toString()} />
{/each}
</Select>
{:else if filter.key && isDate(filter.key)} {:else if filter.key && isDate(filter.key)}
<DatePicker <DatePicker
bind:value={filter.value} bind:value={filter.value}
placeholder={filter.key || fields[0]} /> placeholder={filter.key || fields[0]} />
{:else if filter.key && isNumber(filter.key)} {:else if filter.key && isNumber(filter.key)}
<Input <Input
thin
bind:value={filter.value} bind:value={filter.value}
placeholder={filter.key || fields[0]} placeholder={filter.key || fields[0]}
type="number" /> type="number" />
{:else} {:else}
<Input <Input
thin
placeholder={filter.key || fields[0]} placeholder={filter.key || fields[0]}
bind:value={filter.value} /> bind:value={filter.value} />
{/if} {/if}
@ -171,7 +162,7 @@
</div> </div>
<div class="buttons"> <div class="buttons">
<Button secondary on:click={onClosed}>Cancel</Button> <Button secondary on:click={onClosed}>Cancel</Button>
<Button primary on:click={saveView}>Save</Button> <Button cta on:click={saveView}>Save</Button>
</div> </div>
</div> </div>
</div> </div>
@ -180,6 +171,7 @@
.actions { .actions {
display: grid; display: grid;
grid-gap: var(--spacing-xl); grid-gap: var(--spacing-xl);
padding: var(--spacing-xl);
} }
h5 { h5 {

View file

@ -22,26 +22,20 @@
</script> </script>
<div class="actions"> <div class="actions">
<h5>Group</h5> <h5>Group By</h5>
<div class="input-group-row"> <Select bind:value={view.groupBy} options={fields} />
<p>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"> <div class="footer">
<Button secondary on:click={onClosed}>Cancel</Button> <Button secondary on:click={onClosed}>Cancel</Button>
<Button primary on:click={saveView}>Save</Button> <Button cta on:click={saveView}>Save</Button>
</div> </div>
</div> </div>
<style> <style>
.actions { .actions {
display: grid; display: grid;
width: 200px;
grid-gap: var(--spacing-xl); grid-gap: var(--spacing-xl);
padding: var(--spacing-xl);
} }
h5 { h5 {

View file

@ -2,6 +2,7 @@
import { roles, permissions as permissionsStore } from "stores/backend" import { roles, permissions as permissionsStore } from "stores/backend"
import { notifications } from "@budibase/bbui" import { notifications } from "@budibase/bbui"
import { Button, Label, Input, Select, Spacer } from "@budibase/bbui" import { Button, Label, Input, Select, Spacer } from "@budibase/bbui"
import { capitalise } from "../../../../helpers"
export let resourceId export let resourceId
export let permissions export let permissions
@ -34,16 +35,13 @@
<Label extraSmall grey>Level</Label> <Label extraSmall grey>Level</Label>
<Label extraSmall grey>Role</Label> <Label extraSmall grey>Role</Label>
{#each Object.keys(permissions) as level} {#each Object.keys(permissions) as level}
<Input secondary thin value={level} disabled={true} /> <Input value={capitalise(level)} disabled />
<Select <Select
secondary
thin
value={permissions[level]} value={permissions[level]}
on:change={e => changePermission(level, e.target.value)}> on:change={e => changePermission(level, e.detail)}
{#each $roles as role} options={$roles}
<option value={role._id}>{role.name}</option> getOptionLabel={x => x.name}
{/each} getOptionValue={x => x._id} />
</Select>
{/each} {/each}
</div> </div>
@ -58,6 +56,7 @@
.popover { .popover {
display: grid; display: grid;
width: 400px; width: 400px;
padding: var(--spacing-xl);
} }
h5 { h5 {

View file

@ -1,6 +1,7 @@
<script> <script>
import { Label, Input, TextArea, Spacer } from "@budibase/bbui" import { Label, Input, Spacer } from "@budibase/bbui"
import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte" import KeyValueBuilder from "components/integration/KeyValueBuilder.svelte"
import { capitalise } from "../../../../helpers"
export let integration export let integration
export let schema export let schema
@ -11,16 +12,15 @@
<form> <form>
{#each Object.keys(schema) as configKey} {#each Object.keys(schema) as configKey}
{#if schema[configKey].type === 'object'} {#if schema[configKey].type === 'object'}
<Label small>{configKey}</Label> <Label>{capitalise(configKey)}</Label>
<Spacer small /> <Spacer small />
<KeyValueBuilder <KeyValueBuilder
defaults={schema[configKey].default} defaults={schema[configKey].default}
bind:object={integration[configKey]} /> bind:object={integration[configKey]} />
{:else} {:else}
<div class="form-row"> <div class="form-row">
<Label small>{configKey}</Label> <Label>{capitalise(configKey)}</Label>
<Input <Input
outline
type={schema[configKey].type} type={schema[configKey].type}
on:change on:change
bind:value={integration[configKey]} /> bind:value={integration[configKey]} />

View file

@ -47,11 +47,10 @@
disabled={error || !name}> disabled={error || !name}>
<Input <Input
data-cy="datasource-name-input" data-cy="datasource-name-input"
thin
label="Datasource Name" label="Datasource Name"
on:input={checkValid} on:input={checkValid}
bind:value={name} bind:value={name}
{error} /> {error} />
<Label grey extraSmall>Source</Label> <Label>Source</Label>
<TableIntegrationMenu bind:integration /> <TableIntegrationMenu bind:integration />
</ModalContent> </ModalContent>

View file

@ -91,9 +91,24 @@
} }
const handleTypeChange = column => evt => { const handleTypeChange = column => evt => {
schema[column].type = evt.target.value schema[column].type = evt.detail
validateCSV() validateCSV()
} }
const typeOptions = [
{
label: "Text",
value: "string",
},
{
label: "Number",
value: "number",
},
{
label: "Date",
value: "datetime",
},
]
</script> </script>
<div class="dropzone"> <div class="dropzone">
@ -102,20 +117,18 @@
{#if files[0]}{files[0].name}{:else}Upload{/if} {#if files[0]}{files[0].name}{:else}Upload{/if}
</label> </label>
</div> </div>
<div class="schema-fields"> {#if fields.length}
{#if fields.length} <div class="schema-fields">
{#each fields as columnName} {#each fields as columnName}
<div class="field"> <div class="field">
<span>{columnName}</span> <span>{columnName}</span>
<Select <Select
secondary
thin
bind:value={schema[columnName].type} bind:value={schema[columnName].type}
on:change={handleTypeChange(columnName)}> on:change={handleTypeChange(columnName)}
<option value={'string'}>Text</option> options={typeOptions}
<option value={'number'}>Number</option> placeholder={null}
<option value={'datetime'}>Date</option> getOptionLabel={option => option.label}
</Select> getOptionValue={option => option.value} />
<span class="field-status" class:error={!schema[columnName].success}> <span class="field-status" class:error={!schema[columnName].success}>
{schema[columnName].success ? 'Success' : 'Failure'} {schema[columnName].success ? 'Success' : 'Failure'}
</span> </span>
@ -124,16 +137,15 @@
on:click={() => omitColumn(columnName)} /> on:click={() => omitColumn(columnName)} />
</div> </div>
{/each} {/each}
{/if} </div>
</div> {/if}
{#if fields.length} {#if fields.length}
<div class="display-column"> <div class="display-column">
<Label extraSmall grey>Display Column</Label> <Select
<Select thin secondary bind:value={primaryDisplay}> label="Display Column"
{#each fields as field} bind:value={primaryDisplay}
<option value={field}>{field}</option> options={fields} />
{/each}
</Select>
</div> </div>
{/if} {/if}
@ -165,6 +177,10 @@
display: none; display: none;
} }
.schema-fields {
margin-top: var(--spacing-xl);
}
label { label {
font-family: var(--font-sans); font-family: var(--font-sans);
cursor: pointer; cursor: pointer;
@ -201,7 +217,7 @@
.field { .field {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: 2fr 2fr 1fr auto;
margin-top: var(--spacing-m); margin-top: var(--spacing-m);
align-items: center; align-items: center;
grid-gap: var(--spacing-m); grid-gap: var(--spacing-m);

View file

@ -3,7 +3,7 @@
import { store } from "builderStore" import { store } from "builderStore"
import { tables } from "stores/backend" import { tables } from "stores/backend"
import { notifications } from "@budibase/bbui" import { notifications } from "@budibase/bbui"
import { Input, Label, ModalContent, Toggle } from "@budibase/bbui" import { Input, Label, ModalContent, Toggle, Divider } from "@budibase/bbui"
import TableDataImport from "../TableDataImport.svelte" import TableDataImport from "../TableDataImport.svelte"
import analytics from "analytics" import analytics from "analytics"
import screenTemplates from "builderStore/store/screenTemplates" import screenTemplates from "builderStore/store/screenTemplates"
@ -105,27 +105,20 @@
<Label extraSmall grey>Auto Columns</Label> <Label extraSmall grey>Auto Columns</Label>
<div class="toggles"> <div class="toggles">
<div class="toggle-1"> <div class="toggle-1">
<Toggle <Toggle text="Created by" bind:value={autoColumns.createdBy.enabled} />
text="Created by" <Toggle text="Created at" bind:value={autoColumns.createdAt.enabled} />
bind:checked={autoColumns.createdBy.enabled} /> <Toggle text="Auto ID" bind:value={autoColumns.autoID.enabled} />
<Toggle
text="Created at"
bind:checked={autoColumns.createdAt.enabled} />
<Toggle text="Auto ID" bind:checked={autoColumns.autoID.enabled} />
</div> </div>
<div class="toggle-2"> <div class="toggle-2">
<Toggle <Toggle text="Updated by" bind:value={autoColumns.updatedBy.enabled} />
text="Updated by" <Toggle text="Updated at" bind:value={autoColumns.updatedAt.enabled} />
bind:checked={autoColumns.updatedBy.enabled} />
<Toggle
text="Updated at"
bind:checked={autoColumns.updatedAt.enabled} />
</div> </div>
</div> </div>
<Divider />
</div> </div>
<Toggle <Toggle
text="Generate screens in the design section" text="Generate screens in Design section"
bind:checked={createAutoscreens} /> bind:value={createAutoscreens} />
<div> <div>
<Label grey extraSmall>Create Table from CSV (Optional)</Label> <Label grey extraSmall>Create Table from CSV (Optional)</Label>
<TableDataImport bind:dataImport /> <TableDataImport bind:dataImport />
@ -134,8 +127,7 @@
<style> <style>
.autocolumns { .autocolumns {
padding-bottom: 10px; margin-bottom: -10px;
border-bottom: 3px solid var(--grey-1);
} }
.toggles { .toggles {

View file

@ -1,114 +0,0 @@
<script>
import { createEventDispatcher } from "svelte"
const dispatch = createEventDispatcher()
export let checked = false
function handleChange() {
checked = !checked
dispatch("change", checked)
}
</script>
<input type="checkbox" class="checkbox" id="_checkbox" />
<div class="checkbox-container" on:click={handleChange}>
<div class="check-div" class:checked>
<div class="tick_mark" />
</div>
</div>
<style>
.checkbox-container {
position: relative;
z-index: 0;
}
.checkbox {
display: none;
}
.check-div {
position: relative;
width: 20px;
height: 20px;
background-color: var(--grey-2);
cursor: pointer;
transition: 0.2s ease transform, 0.2s ease background-color,
0.2s ease box-shadow;
overflow: hidden;
border-radius: 4px;
}
.check-div:before {
content: "";
position: absolute;
top: 50%;
right: 0;
left: 0;
width: 12px;
height: 12px;
margin: 0 auto;
background-color: var(--background);
transform: translateY(-50%);
transition: 0.2s ease width, 0.2s ease height;
border-radius: 2px;
}
.check-div:active {
transform: translateY(-50%) scale(0.9);
}
.tick_mark {
position: absolute;
top: 50%;
left: 6px;
width: 5px;
height: 4px;
margin: 0 auto;
transform: rotateZ(-40deg);
}
.tick_mark:before,
.tick_mark:after {
content: "";
position: absolute;
background-color: var(--ink);
border-radius: 2px;
opacity: 0;
transition: 0.2s ease transform, 0.2s ease opacity;
}
.tick_mark:before {
left: 0;
bottom: 0;
width: 2px;
height: 6px;
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.23);
transform: translateY(-68px);
}
.tick_mark:after {
left: 0;
bottom: 0;
width: 12px;
height: 2px;
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.23);
transform: translateX(78px);
}
.checked {
background-color: var(--grey-2);
}
.checked:before {
width: 0;
height: 0;
}
.checked .tick_mark:before,
.checked .tick_mark:after {
transform: translate(0);
opacity: 1;
}
</style>

View file

@ -1,13 +0,0 @@
<script>
import { DatePicker } from "@budibase/bbui"
export let label
export let value
function onChange(event) {
const [selectedDates] = event.detail
value = selectedDates[0]
}
</script>
<DatePicker {label} on:change={onChange} {value} />

View file

@ -32,12 +32,11 @@
<div class="control"> <div class="control">
<Input <Input
{thin}
value={readableValue} value={readableValue}
on:change={event => onChange(event.target.value)} on:change={event => onChange(event.detail)}
{placeholder} /> {placeholder} />
<div class="icon" on:click={bindingDrawer.show}> <div class="icon" on:click={bindingDrawer.show}>
<Icon name="lightning" /> <Icon s name="FlashOn" />
</div> </div>
</div> </div>
<Drawer bind:this={bindingDrawer} {title}> <Drawer bind:this={bindingDrawer} {title}>
@ -66,23 +65,30 @@
} }
.icon { .icon {
right: 2px; right: 1px;
top: 2px; top: 1px;
bottom: 2px; bottom: 1px;
position: absolute; position: absolute;
justify-content: center;
align-items: center; align-items: center;
display: flex; display: flex;
flex-direction: row;
box-sizing: border-box; box-sizing: border-box;
padding-left: 7px; border-left: 1px solid var(--spectrum-alias-border-color);
border-left: 1px solid var(--grey-4); border-top-right-radius: var(--spectrum-alias-border-radius-regular);
background-color: var(--grey-2); border-bottom-right-radius: var(--spectrum-alias-border-radius-regular);
border-top-right-radius: var(--border-radius-m); width: 31px;
border-bottom-right-radius: var(--border-radius-m); color: var(--spectrum-alias-text-color);
color: var(--grey-7); background-color: var(--spectrum-global-color-gray-75);
font-size: 14px; transition: background-color
var(--spectrum-global-animation-duration-100, 130ms),
box-shadow var(--spectrum-global-animation-duration-100, 130ms),
border-color var(--spectrum-global-animation-duration-100, 130ms);
} }
.icon:hover { .icon:hover {
color: var(--ink);
cursor: pointer; cursor: pointer;
color: var(--spectrum-alias-text-color-hover);
background-color: var(--spectrum-global-color-gray-50);
border-color: var(--spectrum-alias-border-color-hover);
} }
</style> </style>

View file

@ -42,27 +42,18 @@
{:else} {:else}
{#if schema.relationshipType === 'one-to-many'} {#if schema.relationshipType === 'one-to-many'}
<Select <Select
thin value={linkedIds?.[0]}
secondary options={rows}
on:change={e => (linkedIds = e.target.value ? [e.target.value] : [])} getOptionLabel={getPrettyName}
name={label} getOptionValue={row => row._id}
{label}> on:change={e => (linkedIds = e.detail ? [e.detail] : [])}
<option value="">Choose an option</option> {label} />
{#each rows as row}
<option selected={row._id === linkedIds[0]} value={row._id}>
{getPrettyName(row)}
</option>
{/each}
</Select>
{:else} {:else}
<Multiselect <Multiselect
secondary
bind:value={linkedIds} bind:value={linkedIds}
{label} {label}
placeholder="Choose some options"> options={rows}
{#each rows as row} getOptionLabel={getPrettyName}
<option value={row._id}>{getPrettyName(row)}</option> getOptionValue={row => row._id} />
{/each}
</Multiselect>
{/if} {/if}
{/if} {/if}

View file

@ -62,7 +62,6 @@
background-color: var(--grey-3); background-color: var(--grey-3);
} }
.nav-item:hover .actions { .nav-item:hover .actions {
display: flex;
visibility: visible; visibility: visible;
} }

View file

@ -8,12 +8,11 @@
screenSearchString, screenSearchString,
} from "builderStore" } from "builderStore"
import { roles } from "stores/backend" import { roles } from "stores/backend"
import { FrontendTypes } from "constants"
import ComponentNavigationTree from "components/design/NavigationPanel/ComponentNavigationTree/index.svelte" import ComponentNavigationTree from "components/design/NavigationPanel/ComponentNavigationTree/index.svelte"
import Layout from "components/design/NavigationPanel/Layout.svelte" import Layout from "components/design/NavigationPanel/Layout.svelte"
import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte" import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte"
import NewLayoutModal from "components/design/NavigationPanel/NewLayoutModal.svelte" import NewLayoutModal from "components/design/NavigationPanel/NewLayoutModal.svelte"
import { Modal, Switcher, Select, Input, Tabs, Tab } from "@budibase/bbui" import { Modal, Select, Search, Tabs, Tab } from "@budibase/bbui"
const tabs = [ const tabs = [
{ {
@ -29,13 +28,13 @@
let modal let modal
$: selected = tabs.find(t => t.key === $params.assetType).title $: selected = tabs.find(t => t.key === $params.assetType).title
const navigate = ({detail}) => { const navigate = ({ detail }) => {
const { key } = tabs.find(t => t.title === detail) const { key } = tabs.find(t => t.title === detail)
$goto(`../${key}`) $goto(`../${key}`)
} }
const updateAccessRole = event => { const updateAccessRole = event => {
const role = event.target.value const role = event.detail
// Select a valid screen with this new role - otherwise we'll not be // Select a valid screen with this new role - otherwise we'll not be
// able to change role at all because ComponentNavigationTree will kick us // able to change role at all because ComponentNavigationTree will kick us
@ -70,53 +69,41 @@
<div class="tab-content-padding"> <div class="tab-content-padding">
<div class="role-select"> <div class="role-select">
<Select <Select
extraThin
secondary
on:change={updateAccessRole} on:change={updateAccessRole}
value={$selectedAccessRole} value={$selectedAccessRole}
label="Filter by Access"> label="Filter by Access"
{#each $roles as role} getOptionLabel={role => role.name}
<option value={role._id}>{role.name}</option> getOptionValue={role => role._id}
{/each} options={$roles} />
</Select> <Search
<div class="search-screens"> placeholder="Enter a route to search"
<Input label="Search Screens"
extraThin bind:value={$screenSearchString} />
placeholder="Enter a route to search" </div>
label="Search Screens" <div class="nav-items-container">
bind:value={$screenSearchString} /> <ComponentNavigationTree />
<i </div>
class="ri-close-line" <Modal bind:this={modal}>
on:click={() => ($screenSearchString = null)} /> <NewScreenModal />
</div> </Modal>
</div>
<div class="nav-items-container">
<ComponentNavigationTree />
</div>
<Modal bind:this={modal}>
<NewScreenModal />
</Modal>
</div> </div>
</Tab> </Tab>
<Tab title="Layouts"> <Tab title="Layouts">
<div class="tab-content-padding"> <div class="tab-content-padding">
<i <i
on:click={modal.show} on:click={modal.show}
data-cy="new-layout" data-cy="new-layout"
class="ri-add-circle-fill" /> class="ri-add-circle-fill" />
{#each $store.layouts as layout, idx (layout._id)} {#each $store.layouts as layout, idx (layout._id)}
<Layout {layout} border={idx > 0} /> <Layout {layout} border={idx > 0} />
{/each} {/each}
<Modal bind:this={modal}> <Modal bind:this={modal}>
<NewLayoutModal /> <NewLayoutModal />
</Modal> </Modal>
</div> </div>
</Tab> </Tab>
</Tabs> </Tabs>
<i <i on:click={modal.show} data-cy="new-screen" class="ri-add-circle-fill" />
on:click={modal.show}
data-cy="new-screen"
class="ri-add-circle-fill" />
</div> </div>
<style> <style>

View file

@ -82,8 +82,8 @@
} }
const routeChanged = event => { const routeChanged = event => {
if (!event.target.value.startsWith("/")) { if (!event.detail.startsWith("/")) {
route = "/" + event.target.value route = "/" + event.detail
} }
} }
</script> </script>
@ -92,24 +92,22 @@
<Select <Select
label="Choose a Template" label="Choose a Template"
bind:value={templateIndex} bind:value={templateIndex}
secondary on:change={ev => templateChanged(ev.detail)}
on:change={ev => templateChanged(ev.target.value)}> options={templates}
{#if templates} placeholder={null}
{#each templates as template, index} getOptionLabel={x => x.name}
<option value={index}>{template.name}</option> getOptionValue={(x, idx) => idx} />
{/each}
{/if}
</Select>
<Input label="Name" bind:value={name} /> <Input label="Name" bind:value={name} />
<Input <Input
label="Url" label="Url"
error={routeError} error={routeError}
bind:value={route} bind:value={route}
on:change={routeChanged} /> on:change={routeChanged} />
<Select label="Access" bind:value={roleId} secondary> <Select
{#each $roles as role} label="Access"
<option value={role._id}>{role.name}</option> bind:value={roleId}
{/each} options={$roles}
</Select> getOptionLabel={x => x.name}
<Toggle text="Create link in navigation bar" bind:checked={createLink} /> getOptionValue={x => x._id} />
<Toggle text="Create link in navigation bar" bind:value={createLink} />
</ModalContent> </ModalContent>

View file

@ -1,6 +1,6 @@
<script> <script>
import groupBy from "lodash/fp/groupBy" import groupBy from "lodash/fp/groupBy"
import { Input, TextArea, Heading, Spacer, Label } from "@budibase/bbui" import { Search, TextArea, Heading, Spacer, Label } from "@budibase/bbui"
import { createEventDispatcher } from "svelte" import { createEventDispatcher } from "svelte"
import { isValid } from "@budibase/string-templates" import { isValid } from "@budibase/string-templates"
import { import {
@ -60,7 +60,7 @@
<div class="drawer-contents"> <div class="drawer-contents">
<div class="container" data-cy="binding-dropdown-modal"> <div class="container" data-cy="binding-dropdown-modal">
<div class="list"> <div class="list">
<Input extraThin placeholder="Search" bind:value={search} /> <Search placeholder="Search" bind:value={search} />
<Spacer medium /> <Spacer medium />
{#if context} {#if context}
<Heading xs h3>Columns</Heading> <Heading xs h3>Columns</Heading>
@ -109,10 +109,8 @@
<div class="text"> <div class="text">
<TextArea <TextArea
bind:getCaretPosition bind:getCaretPosition
thin
bind:value bind:value
placeholder="Add text, or click the objects on the left to add them to placeholder="Add text, or click the objects on the left to add them to the textbox." />
the textbox." />
{#if !valid} {#if !valid}
<p class="syntax-error"> <p class="syntax-error">
Current Handlebars syntax is invalid, please check the guide Current Handlebars syntax is invalid, please check the guide
@ -145,7 +143,7 @@
font-family: var(--font-sans); font-family: var(--font-sans);
} }
.text :global(textarea) { .text :global(textarea) {
min-height: 100px; min-height: 200px;
} }
.text :global(p) { .text :global(p) {
margin: 0; margin: 0;

View file

@ -63,7 +63,7 @@
<div class="custom-styles"> <div class="custom-styles">
<TextArea <TextArea
value={componentInstance._styles.custom} value={componentInstance._styles.custom}
on:change={event => onCustomStyleChanged(event.target.value)} on:change={event => onCustomStyleChanged(event.detail)}
placeholder="Enter some CSS..." /> placeholder="Enter some CSS..." />
</div> </div>
</DetailSummary> </DetailSummary>
@ -79,15 +79,11 @@
<div class="transitions"> <div class="transitions">
<Select <Select
value={componentInstance._transition} value={componentInstance._transition}
on:change={event => onUpdateTransition(event.target.value)} on:change={event => onUpdateTransition(event.detail)}
name="transition" name="transition"
label="Transition" label="Transition"
secondary options={transitions}
thin> getOptionLabel={capitalize} />
{#each transitions as transition}
<option value={transition}>{capitalize(transition)}</option>
{/each}
</Select>
</div> </div>
{/if} {/if}
</div> </div>
@ -105,6 +101,7 @@
position: relative; position: relative;
display: flex; display: flex;
min-height: 0; min-height: 0;
flex: 1 1 auto;
} }
.property-groups { .property-groups {

View file

@ -2,4 +2,4 @@
import FormFieldSelect from "./FormFieldSelect.svelte" import FormFieldSelect from "./FormFieldSelect.svelte"
</script> </script>
<FormFieldSelect {...$$props} type="attachment" /> <FormFieldSelect {...$$props} on:change type="attachment" />

View file

@ -2,4 +2,4 @@
import FormFieldSelect from "./FormFieldSelect.svelte" import FormFieldSelect from "./FormFieldSelect.svelte"
</script> </script>
<FormFieldSelect {...$$props} type="boolean" /> <FormFieldSelect {...$$props} on:change type="boolean" />

View file

@ -1,7 +0,0 @@
<script>
import Checkbox from "components/common/Checkbox.svelte"
export let value
</script>
<Checkbox checked={value} on:change />

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