merge spectrum-bbui
This commit is contained in:
commit
a84b1ed316
162 changed files with 2479 additions and 9516 deletions
|
@ -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"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"]()
|
|
||||||
|
|
|
@ -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)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -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>
|
|
39
packages/bbui/src/Form/Combobox.svelte
Normal file
39
packages/bbui/src/Form/Combobox.svelte
Normal 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>
|
43
packages/bbui/src/Form/Core/Checkbox.svelte
Normal file
43
packages/bbui/src/Form/Core/Checkbox.svelte
Normal 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>
|
128
packages/bbui/src/Form/Core/Combobox.svelte
Normal file
128
packages/bbui/src/Form/Core/Combobox.svelte
Normal 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>
|
142
packages/bbui/src/Form/Core/DatePicker.svelte
Normal file
142
packages/bbui/src/Form/Core/DatePicker.svelte
Normal 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>
|
81
packages/bbui/src/Form/Core/Multiselect.svelte
Normal file
81
packages/bbui/src/Form/Core/Multiselect.svelte
Normal 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} />
|
123
packages/bbui/src/Form/Core/Picker.svelte
Normal file
123
packages/bbui/src/Form/Core/Picker.svelte
Normal 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>
|
36
packages/bbui/src/Form/Core/RadioGroup.svelte
Normal file
36
packages/bbui/src/Form/Core/RadioGroup.svelte
Normal 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>
|
82
packages/bbui/src/Form/Core/Search.svelte
Normal file
82
packages/bbui/src/Form/Core/Search.svelte
Normal 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>
|
57
packages/bbui/src/Form/Core/Select.svelte
Normal file
57
packages/bbui/src/Form/Core/Select.svelte
Normal 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} />
|
27
packages/bbui/src/Form/Core/Switch.svelte
Normal file
27
packages/bbui/src/Form/Core/Switch.svelte
Normal 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>
|
55
packages/bbui/src/Form/Core/TextArea.svelte
Normal file
55
packages/bbui/src/Form/Core/TextArea.svelte
Normal 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>
|
84
packages/bbui/src/Form/Core/TextField.svelte
Normal file
84
packages/bbui/src/Form/Core/TextField.svelte
Normal 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>
|
10
packages/bbui/src/Form/Core/index.js
Normal file
10
packages/bbui/src/Form/Core/index.js
Normal 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"
|
|
@ -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>
|
|
|
@ -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>
|
|
||||||
|
|
29
packages/bbui/src/Form/DatePicker.svelte
Normal file
29
packages/bbui/src/Form/DatePicker.svelte
Normal 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>
|
42
packages/bbui/src/Form/Field.svelte
Normal file
42
packages/bbui/src/Form/Field.svelte
Normal 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>
|
26
packages/bbui/src/Form/FieldLabel.svelte
Normal file
26
packages/bbui/src/Form/FieldLabel.svelte
Normal 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>
|
|
@ -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>
|
|
||||||
|
|
|
@ -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>
|
|
|
@ -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"> </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>
|
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
|
@ -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>
|
|
37
packages/bbui/src/Form/RadioGroup.svelte
Normal file
37
packages/bbui/src/Form/RadioGroup.svelte
Normal 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>
|
|
@ -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>
|
|
|
@ -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>
|
|
27
packages/bbui/src/Form/Search.svelte
Normal file
27
packages/bbui/src/Form/Search.svelte
Normal 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>
|
|
@ -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>
|
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
||||||
|
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -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>
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -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>
|
|
@ -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>
|
|
|
@ -1,8 +0,0 @@
|
||||||
<script>
|
|
||||||
import Search from "./Search.svelte";
|
|
||||||
import { View } from "svench";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<View name="Name">
|
|
||||||
<Search />
|
|
||||||
</View>
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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}
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
|
@ -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)
|
||||||
}
|
}
|
|
@ -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
|
|
||||||
}
|
|
8
packages/bbui/src/utils/helpers.js
Normal file
8
packages/bbui/src/utils/helpers.js
Normal 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}`
|
||||||
|
}
|
|
@ -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
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
})
|
})
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
|
|
@ -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() {}
|
|
||||||
}
|
|
|
@ -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>
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
|
||||||
|
|
|
@ -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} />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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]} />
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
|
|
@ -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} />
|
|
|
@ -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>
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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" />
|
||||||
|
|
|
@ -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
Loading…
Reference in a new issue