Improve modal layout, improve modal button spacing, use rollup for building to expose multple BBUI entrypoints
This commit is contained in:
parent
75a58829a6
commit
b741d9e9d9
|
@ -2,38 +2,35 @@
|
||||||
"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": {
|
||||||
".": {
|
".": {
|
||||||
"import": "./dist/bbui.es.js"
|
"import": "./dist/bbui.es.js"
|
||||||
},
|
},
|
||||||
|
"./internal": {
|
||||||
|
"import": "./dist/internal.es.js"
|
||||||
|
},
|
||||||
"./package.json": "./package.json",
|
"./package.json": "./package.json",
|
||||||
"./dist/style.css": "./dist/style.css"
|
"./dist/style.css": "./dist/style.css"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:builder": "vite build",
|
"dev:builder": "rollup -cw",
|
||||||
"build": "vite build"
|
"build": "rollup -c"
|
||||||
},
|
},
|
||||||
"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"
|
||||||
|
@ -50,20 +47,22 @@
|
||||||
"@spectrum-css/checkbox": "^3.0.1",
|
"@spectrum-css/checkbox": "^3.0.1",
|
||||||
"@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/fieldlabel": "^3.0.1",
|
||||||
"@spectrum-css/icon": "^3.0.1",
|
"@spectrum-css/icon": "^3.0.1",
|
||||||
"@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/table": "^3.0.1",
|
"@spectrum-css/table": "^3.0.1",
|
||||||
|
"@spectrum-css/textfield": "^3.0.1",
|
||||||
"@spectrum-css/toast": "^3.0.1",
|
"@spectrum-css/toast": "^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",
|
"markdown-it": "^12.0.4",
|
||||||
"quill": "^1.3.7",
|
"quill": "^1.3.7",
|
||||||
"sirv-cli": "^0.4.6",
|
|
||||||
"svelte-flatpickr": "^2.4.0",
|
"svelte-flatpickr": "^2.4.0",
|
||||||
"svelte-portal": "^1.0.0",
|
"svelte-portal": "^1.0.0",
|
||||||
"turndown": "^7.0.0"
|
"turndown": "^7.0.0"
|
||||||
|
|
|
@ -1,140 +1,30 @@
|
||||||
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
|
const makeConfig = ({ input, name }) => ({
|
||||||
const SVENCH = !!process.env.SVENCH
|
input,
|
||||||
const HOT = WATCH
|
output: {
|
||||||
const PRODUCTION = !WATCH
|
sourcemap: true,
|
||||||
|
format: "esm",
|
||||||
const svench = Svench({
|
file: `dist/${name}.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",
|
|
||||||
},
|
|
||||||
|
|
||||||
extensions: [".svench", ".svench.svelte", ".svench.svx", ".md"],
|
|
||||||
|
|
||||||
serve: WATCH && {
|
|
||||||
host: "0.0.0.0",
|
|
||||||
port: 4242,
|
|
||||||
public: "public",
|
|
||||||
nollup: "0.0.0.0:42421",
|
|
||||||
},
|
},
|
||||||
|
plugins: [
|
||||||
|
resolve(),
|
||||||
|
commonjs(),
|
||||||
|
svelte({
|
||||||
|
emitCss: true,
|
||||||
|
}),
|
||||||
|
postcss(),
|
||||||
|
terser(),
|
||||||
|
json(),
|
||||||
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
// NOTE configs are in function form to avoid instantiating plugins of the
|
export default [
|
||||||
// config that is not used for nothing (in particular, the HMR plugin launches
|
makeConfig({ input: "src/index.js", name: "bbui" }),
|
||||||
// a dev server on startup, this is not desired when just building for prod)
|
makeConfig({ input: "src/Form/internal/index.js", name: "internal" }),
|
||||||
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"]()
|
|
||||||
|
|
|
@ -4,16 +4,15 @@
|
||||||
export let disabled = false
|
export let disabled = false
|
||||||
|
|
||||||
/** @type {('S', 'M', 'L', 'XL')} Size of button */
|
/** @type {('S', 'M', 'L', 'XL')} Size of button */
|
||||||
export let size = "M";
|
export let size = "M"
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
export let cta, primary, secondary, warning, overBackground;
|
export let cta, primary, secondary, warning, overBackground
|
||||||
|
|
||||||
export let quiet = false
|
export let quiet = false
|
||||||
|
|
||||||
export let icon = undefined;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
export let icon = undefined
|
||||||
|
</script>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class:spectrum-Button--cta={cta}
|
class:spectrum-Button--cta={cta}
|
||||||
|
@ -26,15 +25,18 @@
|
||||||
{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}
|
||||||
{#if $$slots}
|
{#if $$slots}
|
||||||
<span class="spectrum-Button-label"><slot /></span>
|
<span class="spectrum-Button-label"><slot /></span>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
<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'}>
|
||||||
|
<FieldLabel forId={id} {label} position={labelPosition} />
|
||||||
|
<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>
|
|
@ -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,29 @@
|
||||||
<script>
|
<script>
|
||||||
|
import Field from "./Field.svelte"
|
||||||
|
import TextField from "./internal/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 error = null
|
||||||
export let placeholder = ""
|
|
||||||
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 => {
|
||||||
|
dispatch("change", e.detail)
|
||||||
const updateValue = e => {
|
value = 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>
|
{value}
|
||||||
{/if}
|
{placeholder}
|
||||||
{#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}
|
|
||||||
<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} />
|
</Field>
|
||||||
{#if error}
|
|
||||||
<div class="error">{error}</div>
|
|
||||||
{/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,324 +1,33 @@
|
||||||
<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 "./internal/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"
|
export let value = null
|
||||||
import clickOutside from "../Actions/click_outside"
|
export let label = undefined
|
||||||
|
export let disabled = 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
|
||||||
|
|
||||||
const dispatch = createEventDispatcher()
|
const dispatch = createEventDispatcher()
|
||||||
|
const onChange = e => {
|
||||||
export let value = []
|
dispatch("change", e.detail)
|
||||||
export let label = undefined
|
value = e.detail
|
||||||
export let align = "left"
|
|
||||||
export let secondary = false
|
|
||||||
export let outline = false
|
|
||||||
export let disabled = false
|
|
||||||
export let placeholder = undefined
|
|
||||||
export let extraThin = false
|
|
||||||
|
|
||||||
let options = []
|
|
||||||
let optionsVisible = false
|
|
||||||
let slot
|
|
||||||
let anchor
|
|
||||||
$: 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
|
</Field>
|
||||||
class:optionsVisible
|
|
||||||
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,95 +1,33 @@
|
||||||
<script>
|
<script>
|
||||||
import Icon from "../Icons/Icon.svelte"
|
import Field from "./Field.svelte"
|
||||||
import Label from "../Styleguide/Label.svelte"
|
import Select from "./internal/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 labelPosition = "above"
|
||||||
|
export let error = null
|
||||||
|
export let placeholder = null
|
||||||
|
export let options = []
|
||||||
|
export let getOptionLabel = option => option
|
||||||
|
export let getOptionValue = option => option
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
const onChange = e => {
|
||||||
|
dispatch("change", e.detail)
|
||||||
|
value = e.detail
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div>
|
<Field {label} {labelPosition} {disabled} {error}>
|
||||||
{#if label}
|
<Select
|
||||||
<Label extraSmall grey forAttr={name}>{label}</Label>
|
{error}
|
||||||
{/if}
|
{disabled}
|
||||||
<div class="relative">
|
{value}
|
||||||
<select
|
{options}
|
||||||
{name}
|
{placeholder}
|
||||||
class:thin
|
{getOptionLabel}
|
||||||
class:extraThin
|
{getOptionValue}
|
||||||
class:secondary
|
on:change={onChange} />
|
||||||
class:outline
|
</Field>
|
||||||
{disabled}
|
|
||||||
on:change
|
|
||||||
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>
|
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
<script>
|
||||||
|
import Picker from "./Picker.svelte"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let value = []
|
||||||
|
export let fieldId = 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()
|
||||||
|
$: fieldText = getFieldText(value)
|
||||||
|
$: valueLookupMap = getValueLookupMap(value)
|
||||||
|
$: isOptionSelected = option => valueLookupMap[option] === true
|
||||||
|
|
||||||
|
const getFieldText = value => {
|
||||||
|
if (value?.length) {
|
||||||
|
const count = value?.length ?? 0
|
||||||
|
return `${count} selected option${count === 1 ? "" : "s"}`
|
||||||
|
} else {
|
||||||
|
return placeholder || "Choose some options"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getValueLookupMap = value => {
|
||||||
|
let map = {}
|
||||||
|
if (value?.length) {
|
||||||
|
value.forEach(option => {
|
||||||
|
const optionValue = getOptionValue(option)
|
||||||
|
if (optionValue) {
|
||||||
|
map[optionValue] = true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return map
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleOption = option => {
|
||||||
|
if (valueLookupMap[option]) {
|
||||||
|
const filtered = value.filter(option => option !== id)
|
||||||
|
dispatch("change", filtered)
|
||||||
|
} else {
|
||||||
|
dispatch("change", [...value, option])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Picker
|
||||||
|
{fieldId}
|
||||||
|
{error}
|
||||||
|
{disabled}
|
||||||
|
{fieldText}
|
||||||
|
{options}
|
||||||
|
isPlaceholder={!value?.length}
|
||||||
|
{isOptionSelected}
|
||||||
|
{getOptionLabel}
|
||||||
|
{getOptionValue}
|
||||||
|
onSelectOption={toggleOption} />
|
|
@ -0,0 +1,109 @@
|
||||||
|
<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"
|
||||||
|
|
||||||
|
export let fieldId = 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
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<button
|
||||||
|
id={fieldId}
|
||||||
|
class="spectrum-Picker spectrum-Picker--sizeM"
|
||||||
|
{disabled}
|
||||||
|
class:is-invalid={!!error}
|
||||||
|
class:is-open={open}
|
||||||
|
aria-haspopup="listbox"
|
||||||
|
on:click={() => (open = true)}>
|
||||||
|
<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 class="overlay" on:mousedown|self={() => (open = false)} />
|
||||||
|
<div
|
||||||
|
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}
|
||||||
|
{#each options as option}
|
||||||
|
<li
|
||||||
|
class="spectrum-Menu-item"
|
||||||
|
class:is-selected={isOptionSelected(getOptionValue(option))}
|
||||||
|
role="option"
|
||||||
|
aria-selected="true"
|
||||||
|
tabindex="0"
|
||||||
|
on:click={() => onSelectOption(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}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.overlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
.spectrum-Popover {
|
||||||
|
max-height: 240px;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 999;
|
||||||
|
}
|
||||||
|
.spectrum-Picker {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,42 @@
|
||||||
|
<script>
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
import Picker from "./Picker.svelte"
|
||||||
|
|
||||||
|
export let value = null
|
||||||
|
export let fieldId = 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()
|
||||||
|
let open = false
|
||||||
|
$: placeholderText = placeholder || "Choose an option"
|
||||||
|
$: isNull = value == null || value === ""
|
||||||
|
$: selectedOption = options.find(option => getOptionValue(option) === value)
|
||||||
|
$: selectedLabel = selectedOption
|
||||||
|
? getOptionLabel(selectedOption)
|
||||||
|
: placeholderText
|
||||||
|
$: fieldText = isNull ? placeholderText : selectedLabel
|
||||||
|
|
||||||
|
const selectOption = value => {
|
||||||
|
dispatch("change", value)
|
||||||
|
open = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Picker
|
||||||
|
bind:open
|
||||||
|
{fieldId}
|
||||||
|
{error}
|
||||||
|
{disabled}
|
||||||
|
{fieldText}
|
||||||
|
{options}
|
||||||
|
{getOptionLabel}
|
||||||
|
{getOptionValue}
|
||||||
|
isPlaceholder={isNull}
|
||||||
|
placeholderOption={placeholderText}
|
||||||
|
isOptionSelected={option => option === value}
|
||||||
|
onSelectOption={selectOption} />
|
|
@ -0,0 +1,60 @@
|
||||||
|
<script>
|
||||||
|
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||||
|
import { createEventDispatcher } from "svelte"
|
||||||
|
|
||||||
|
export let value = ""
|
||||||
|
export let placeholder = ""
|
||||||
|
export let type = "text"
|
||||||
|
export let disabled = false
|
||||||
|
export let error = null
|
||||||
|
export let id = null
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher()
|
||||||
|
|
||||||
|
const updateValue = value => {
|
||||||
|
if (type === "number") {
|
||||||
|
const float = parseFloat(value)
|
||||||
|
value = isNaN(float) ? null : float
|
||||||
|
}
|
||||||
|
dispatch("change", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const onBlur = event => {
|
||||||
|
updateValue(event.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateValueOnEnter = event => {
|
||||||
|
if (event.key === "Enter") {
|
||||||
|
updateValue(event.target.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="spectrum-Textfield"
|
||||||
|
class:is-invalid={!!error}
|
||||||
|
class:is-disabled={disabled}>
|
||||||
|
{#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:keyup={updateValueOnEnter}
|
||||||
|
{disabled}
|
||||||
|
{id}
|
||||||
|
value={value || ''}
|
||||||
|
placeholder={placeholder || ''}
|
||||||
|
on:blur={onBlur}
|
||||||
|
{type}
|
||||||
|
class="spectrum-Textfield-input" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.spectrum-Textfield {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as TextField } from "./TextField.svelte"
|
||||||
|
export { default as Select } from "./Select.svelte"
|
|
@ -38,12 +38,56 @@
|
||||||
|
|
||||||
{#if visible}
|
{#if visible}
|
||||||
<Portal target=".modal-container">
|
<Portal target=".modal-container">
|
||||||
<div 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:click|self={hide}>
|
||||||
|
<div class="modal-wrapper" on:click|self={hide}>
|
||||||
|
<div class="modal-inner-wrapper" on:click|self={hide}>
|
||||||
|
<div
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
@ -29,36 +29,37 @@
|
||||||
}
|
}
|
||||||
</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">
|
||||||
<h1 class="spectrum-Dialog-heading">{title}</h1>
|
<h1 class="spectrum-Dialog-heading">{title}</h1>
|
||||||
<hr class="spectrum-Divider spectrum-Divider--sizeS spectrum-Divider--horizontal spectrum-Dialog-divider">
|
<hr
|
||||||
|
class="spectrum-Divider spectrum-Divider--sizeS spectrum-Divider--horizontal spectrum-Dialog-divider" />
|
||||||
|
|
||||||
<!-- 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 +70,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.content-grid {
|
.content-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -78,6 +78,14 @@
|
||||||
color: var(--ink);
|
color: var(--ink);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.spectrum-Dialog-content {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.spectrum-Dialog-buttonGroup {
|
||||||
|
gap: var(--spectrum-global-dimension-static-size-200);
|
||||||
|
}
|
||||||
|
|
||||||
h1 {
|
h1 {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
@ -95,27 +103,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>
|
||||||
|
|
|
@ -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
|
||||||
|
@ -311,8 +311,6 @@
|
||||||
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-primary);
|
||||||
|
@ -421,14 +419,16 @@
|
||||||
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;
|
||||||
}
|
}
|
||||||
tr:first-child td {
|
tr:first-child td {
|
||||||
border-top: none !important;
|
border-top: none !important;
|
||||||
}
|
}
|
||||||
|
tr:last-child td {
|
||||||
|
border-bottom: 1px solid
|
||||||
|
var(--spectrum-table-border-color, var(--spectrum-alias-border-color-mid)) !important;
|
||||||
|
}
|
||||||
.container:not(.quiet) td.spectrum-Table-cell--divider {
|
.container:not(.quiet) td.spectrum-Table-cell--divider {
|
||||||
width: 1px;
|
width: 1px;
|
||||||
border-right: 1px solid
|
border-right: 1px solid
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue