@budibase/bbui 1.0.49-alpha.7 → 1.0.50-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@budibase/bbui",
3
3
  "description": "A UI solution used in the different Budibase projects.",
4
- "version": "1.0.49-alpha.7",
4
+ "version": "1.0.50-alpha.0",
5
5
  "license": "MPL-2.0",
6
6
  "svelte": "src/index.js",
7
7
  "module": "dist/bbui.es.js",
@@ -79,8 +79,9 @@
79
79
  "@spectrum-css/underlay": "^2.0.9",
80
80
  "@spectrum-css/vars": "^3.0.1",
81
81
  "dayjs": "^1.10.4",
82
+ "easymde": "^2.16.1",
82
83
  "svelte-flatpickr": "^3.2.3",
83
84
  "svelte-portal": "^1.0.0"
84
85
  },
85
- "gitHead": "c7d23bbf7e30545a20c9452d720b7078dafb9947"
86
+ "gitHead": "a076d7ccf1deab0d300ae229a1aa3f4ce92aa1ba"
86
87
  }
@@ -0,0 +1,42 @@
1
+ <script>
2
+ import MarkdownEditor from "../../Markdown/MarkdownEditor.svelte"
3
+
4
+ export let value = ""
5
+ export let placeholder = null
6
+ export let disabled = false
7
+ export let error = null
8
+ export let height = null
9
+ export let id = null
10
+ export let fullScreenOffset = null
11
+ export let easyMDEOptions = null
12
+ </script>
13
+
14
+ <div class:error>
15
+ <MarkdownEditor
16
+ {value}
17
+ {placeholder}
18
+ {height}
19
+ {id}
20
+ {fullScreenOffset}
21
+ {disabled}
22
+ {easyMDEOptions}
23
+ on:change
24
+ />
25
+ </div>
26
+
27
+ <style>
28
+ .error :global(.EasyMDEContainer .editor-toolbar) {
29
+ border-top-color: var(--spectrum-semantic-negative-color-default);
30
+ border-left-color: var(--spectrum-semantic-negative-color-default);
31
+ border-right-color: var(--spectrum-semantic-negative-color-default);
32
+ }
33
+ .error :global(.EasyMDEContainer .CodeMirror) {
34
+ border-bottom-color: var(--spectrum-semantic-negative-color-default);
35
+ border-left-color: var(--spectrum-semantic-negative-color-default);
36
+ border-right-color: var(--spectrum-semantic-negative-color-default);
37
+ }
38
+ .error :global(.EasyMDEContainer .editor-preview-side) {
39
+ border-bottom-color: var(--spectrum-semantic-negative-color-default);
40
+ border-right-color: var(--spectrum-semantic-negative-color-default);
41
+ }
42
+ </style>
@@ -13,6 +13,7 @@
13
13
  start: textarea.selectionStart,
14
14
  end: textarea.selectionEnd,
15
15
  })
16
+ export let align = null
16
17
 
17
18
  let focus = false
18
19
  let textarea
@@ -21,11 +22,23 @@
21
22
  dispatch("change", event.target.value)
22
23
  focus = false
23
24
  }
25
+
26
+ const getStyleString = (attribute, value) => {
27
+ if (!attribute || value == null) {
28
+ return ""
29
+ }
30
+ if (isNaN(value)) {
31
+ return `${attribute}:${value};`
32
+ }
33
+ return `${attribute}:${value}px;`
34
+ }
35
+
36
+ $: heightString = getStyleString("height", height)
37
+ $: minHeightString = getStyleString("min-height", minHeight)
24
38
  </script>
25
39
 
26
40
  <div
27
- style={(height ? `height: ${height}px;` : "") +
28
- (minHeight ? `min-height: ${minHeight}px` : "")}
41
+ style={`${heightString}${minHeightString}`}
29
42
  class="spectrum-Textfield spectrum-Textfield--multiline"
30
43
  class:is-invalid={!!error}
31
44
  class:is-disabled={disabled}
@@ -46,6 +59,7 @@
46
59
  bind:this={textarea}
47
60
  placeholder={placeholder || ""}
48
61
  class="spectrum-Textfield-input"
62
+ style={align ? `text-align: ${align}` : ""}
49
63
  {disabled}
50
64
  {id}
51
65
  on:focus={() => (focus = true)}
@@ -12,6 +12,7 @@
12
12
  export let updateOnChange = true
13
13
  export let quiet = false
14
14
  export let dataCy
15
+ export let align
15
16
 
16
17
  const dispatch = createEventDispatcher()
17
18
  let focus = false
@@ -92,8 +93,9 @@
92
93
  on:input={onInput}
93
94
  on:keyup={updateValueOnEnter}
94
95
  {type}
95
- inputmode={type === "number" ? "decimal" : "text"}
96
96
  class="spectrum-Textfield-input"
97
+ style={align ? `text-align: ${align};` : ""}
98
+ inputmode={type === "number" ? "decimal" : "text"}
97
99
  />
98
100
  </div>
99
101
 
@@ -10,3 +10,4 @@ export { default as CoreSearch } from "./Search.svelte"
10
10
  export { default as CoreDatePicker } from "./DatePicker.svelte"
11
11
  export { default as CoreDropzone } from "./Dropzone.svelte"
12
12
  export { default as CoreStepper } from "./Stepper.svelte"
13
+ export { default as CoreRichTextField } from "./RichTextField.svelte"
@@ -0,0 +1,36 @@
1
+ <script>
2
+ import Field from "./Field.svelte"
3
+ import RichTextField from "./Core/RichTextField.svelte"
4
+ import { createEventDispatcher } from "svelte"
5
+
6
+ export let value = null
7
+ export let label = null
8
+ export let labelPosition = "above"
9
+ export let placeholder = null
10
+ export let disabled = false
11
+ export let error = null
12
+ export let height = null
13
+ export let id = null
14
+ export let fullScreenOffset = null
15
+ export let easyMDEOptions = null
16
+
17
+ const dispatch = createEventDispatcher()
18
+ const onChange = e => {
19
+ value = e.detail
20
+ dispatch("change", e.detail)
21
+ }
22
+ </script>
23
+
24
+ <Field {label} {labelPosition} {error}>
25
+ <RichTextField
26
+ {error}
27
+ {disabled}
28
+ {value}
29
+ {placeholder}
30
+ {height}
31
+ {id}
32
+ {fullScreenOffset}
33
+ {easyMDEOptions}
34
+ on:change={onChange}
35
+ />
36
+ </Field>
@@ -0,0 +1,60 @@
1
+ <script>
2
+ import SpectrumMDE from "./SpectrumMDE.svelte"
3
+ import { createEventDispatcher } from "svelte"
4
+
5
+ export let value = null
6
+ export let height = null
7
+ export let placeholder = null
8
+ export let id = null
9
+ export let fullScreenOffset = 0
10
+ export let disabled = false
11
+ export let easyMDEOptions
12
+
13
+ const dispatch = createEventDispatcher()
14
+
15
+ let latestValue
16
+ let mde
17
+
18
+ // Ensure the value is updated if the value prop changes outside the editor's
19
+ // control
20
+ $: checkValue(value)
21
+ $: mde?.codemirror.on("change", debouncedUpdate)
22
+
23
+ const checkValue = val => {
24
+ if (mde && val !== latestValue) {
25
+ mde.value(val)
26
+ }
27
+ }
28
+
29
+ const debounce = (fn, interval) => {
30
+ let timeout
31
+ return () => {
32
+ clearTimeout(timeout)
33
+ timeout = setTimeout(fn, interval)
34
+ }
35
+ }
36
+
37
+ const update = () => {
38
+ latestValue = mde.value()
39
+ dispatch("change", latestValue)
40
+ }
41
+
42
+ // Debounce the update function to avoid spamming it constantly
43
+ const debouncedUpdate = debounce(update, 250)
44
+ </script>
45
+
46
+ {#key height}
47
+ <SpectrumMDE
48
+ bind:mde
49
+ scroll={true}
50
+ {height}
51
+ {id}
52
+ {fullScreenOffset}
53
+ {disabled}
54
+ easyMDEOptions={{
55
+ initialValue: value,
56
+ placeholder,
57
+ ...easyMDEOptions,
58
+ }}
59
+ />
60
+ {/key}
@@ -0,0 +1,70 @@
1
+ <script>
2
+ import SpectrumMDE from "./SpectrumMDE.svelte"
3
+
4
+ export let value
5
+ export let height
6
+
7
+ let mde
8
+
9
+ // Keep the value up to date
10
+ $: mde && mde.value(value || "")
11
+ $: {
12
+ if (mde && !mde.isPreviewActive()) {
13
+ mde.togglePreview()
14
+ }
15
+ }
16
+ </script>
17
+
18
+ <div class="markdown-viewer" style="height:{height};">
19
+ <SpectrumMDE
20
+ bind:mde
21
+ scroll={false}
22
+ easyMDEOptions={{
23
+ initialValue: value,
24
+ toolbar: false,
25
+ }}
26
+ />
27
+ </div>
28
+
29
+ <style>
30
+ .markdown-viewer {
31
+ overflow: auto;
32
+ }
33
+ /* Remove padding, borders and background colors */
34
+ .markdown-viewer :global(.editor-preview) {
35
+ padding: 0;
36
+ border: none;
37
+ background: transparent;
38
+ }
39
+ .markdown-viewer :global(.CodeMirror) {
40
+ border: none;
41
+ background: transparent;
42
+ padding: 0;
43
+ }
44
+ .markdown-viewer :global(.EasyMDEContainer) {
45
+ background: transparent;
46
+ }
47
+ /* Hide the actual code editor */
48
+ .markdown-viewer :global(.CodeMirror-scroll) {
49
+ display: none;
50
+ }
51
+ /*Hide the scrollbar*/
52
+ .markdown-viewer :global(.CodeMirror-vscrollbar) {
53
+ display: none !important;
54
+ }
55
+ /*Position relatively so we only consume whatever space we need */
56
+ .markdown-viewer :global(.editor-preview-full) {
57
+ position: relative;
58
+ }
59
+ /* Remove margin on the first and last components to fully trim the preview */
60
+ .markdown-viewer :global(.editor-preview-full > :first-child) {
61
+ margin-top: 0;
62
+ }
63
+ .markdown-viewer :global(.editor-preview-full > :last-child) {
64
+ margin-bottom: 0;
65
+ }
66
+ /* Code blocks in preview */
67
+ .markdown-viewer :global(.editor-preview-full pre) {
68
+ background: var(--spectrum-global-color-gray-200);
69
+ }
70
+ </style>
@@ -0,0 +1,184 @@
1
+ <script>
2
+ import EasyMDE from "easymde"
3
+ import "easymde/dist/easymde.min.css"
4
+ import { onMount } from "svelte"
5
+
6
+ export let height = null
7
+ export let scroll = true
8
+ export let easyMDEOptions = null
9
+ export let mde = null
10
+ export let id = null
11
+ export let fullScreenOffset = null
12
+ export let disabled = false
13
+
14
+ let element
15
+
16
+ onMount(() => {
17
+ height = height || "200px"
18
+ mde = new EasyMDE({
19
+ element,
20
+ spellChecker: false,
21
+ status: false,
22
+ unorderedListStyle: "-",
23
+ maxHeight: scroll ? height : undefined,
24
+ minHeight: scroll ? undefined : height,
25
+ ...easyMDEOptions,
26
+ })
27
+
28
+ // Revert the editor when we unmount
29
+ return () => {
30
+ mde.toTextArea()
31
+ }
32
+ })
33
+
34
+ $: styleString = getStyleString(fullScreenOffset)
35
+
36
+ const getStyleString = offset => {
37
+ let string = ""
38
+ string += `--fullscreen-offset-x:${offset?.x || "0px"};`
39
+ string += `--fullscreen-offset-y:${offset?.y || "0px"};`
40
+ return string
41
+ }
42
+ </script>
43
+
44
+ <div class:disabled style={styleString}>
45
+ <textarea disabled {id} bind:this={element} />
46
+ </div>
47
+
48
+ <style>
49
+ /* Disabled styles */
50
+ .disabled :global(textarea) {
51
+ display: none;
52
+ }
53
+ .disabled :global(.CodeMirror-cursor) {
54
+ display: none;
55
+ }
56
+ .disabled :global(.EasyMDEContainer) {
57
+ pointer-events: none;
58
+ }
59
+ .disabled :global(.editor-toolbar button i) {
60
+ color: var(--spectrum-global-color-gray-400);
61
+ }
62
+ .disabled :global(.CodeMirror) {
63
+ color: var(--spectrum-global-color-gray-600);
64
+ }
65
+
66
+ /* Toolbar container */
67
+ :global(.EasyMDEContainer .editor-toolbar) {
68
+ background: var(--spectrum-global-color-gray-50);
69
+ border-top: 1px solid var(--spectrum-alias-border-color);
70
+ border-left: 1px solid var(--spectrum-alias-border-color);
71
+ border-right: 1px solid var(--spectrum-alias-border-color);
72
+ }
73
+ /* Main code mirror instance and default color */
74
+ :global(.EasyMDEContainer .CodeMirror) {
75
+ border: 1px solid var(--spectrum-alias-border-color);
76
+ background: var(--spectrum-global-color-gray-50);
77
+ color: var(--spectrum-alias-text-color);
78
+ }
79
+ /* Toolbar button active state */
80
+ :global(.EasyMDEContainer .editor-toolbar button.active) {
81
+ background: var(--spectrum-global-color-gray-200);
82
+ border-color: var(--spectrum-global-color-gray-400);
83
+ }
84
+ /* Toolbar button hover state */
85
+ :global(.EasyMDEContainer .editor-toolbar button:hover) {
86
+ background: var(--spectrum-global-color-gray-200);
87
+ border-color: var(--spectrum-global-color-gray-400);
88
+ }
89
+ /* Toolbar button color */
90
+ :global(.EasyMDEContainer .editor-toolbar button i) {
91
+ color: var(--spectrum-global-color-gray-800);
92
+ }
93
+ /* Separator between toolbar buttons*/
94
+ :global(.EasyMDEContainer .editor-toolbar i.separator) {
95
+ border-color: var(--spectrum-global-color-gray-300);
96
+ }
97
+ /* Cursor */
98
+ :global(.EasyMDEContainer .CodeMirror-cursor) {
99
+ border-color: var(--spectrum-alias-text-color);
100
+ }
101
+ /* Text selections */
102
+ :global(.EasyMDEContainer .CodeMirror-selectedtext) {
103
+ background: var(--spectrum-global-color-gray-400) !important;
104
+ }
105
+ /* Background of lines containing selected text */
106
+ :global(.EasyMDEContainer .CodeMirror-selected) {
107
+ background: var(--spectrum-global-color-gray-400) !important;
108
+ }
109
+ /* Color of text for images and links */
110
+ :global(.EasyMDEContainer .cm-s-easymde .cm-link) {
111
+ color: var(--spectrum-global-color-gray-600);
112
+ }
113
+ /* Color of URL for images and links */
114
+ :global(.EasyMDEContainer .cm-s-easymde .cm-url) {
115
+ color: var(--spectrum-global-color-gray-500);
116
+ }
117
+ /* Full preview window */
118
+ :global(.EasyMDEContainer .editor-preview) {
119
+ background: var(--spectrum-global-color-gray-50);
120
+ }
121
+ /* Side by side preview window */
122
+ :global(.EasyMDEContainer .editor-preview) {
123
+ border: 1px solid var(--spectrum-alias-border-color);
124
+ }
125
+ /* Code blocks in editor */
126
+ :global(.EasyMDEContainer .cm-s-easymde .cm-comment) {
127
+ background: var(--spectrum-global-color-gray-100);
128
+ }
129
+ /* Code blocks in preview */
130
+ :global(.EasyMDEContainer pre) {
131
+ background: var(--spectrum-global-color-gray-100);
132
+ padding: 4px;
133
+ border-radius: 4px;
134
+ }
135
+ :global(.EasyMDEContainer code) {
136
+ color: #e83e8c;
137
+ }
138
+ :global(.EasyMDEContainer pre code) {
139
+ color: var(--spectrum-alias-text-color);
140
+ }
141
+ /* Block quotes */
142
+ :global(.EasyMDEContainer blockquote) {
143
+ border-left: 4px solid var(--spectrum-global-color-gray-400);
144
+ color: var(--spectrum-global-color-gray-700);
145
+ margin-left: 0;
146
+ padding-left: 20px;
147
+ }
148
+ /* HR's */
149
+ :global(.EasyMDEContainer hr) {
150
+ background-color: var(--spectrum-global-color-gray-300);
151
+ border: none;
152
+ height: 2px;
153
+ }
154
+ /* Tables */
155
+ :global(.EasyMDEContainer td, .EasyMDEContainer th) {
156
+ border-color: var(--spectrum-alias-border-color) !important;
157
+ }
158
+ /* Links */
159
+ :global(.EasyMDEContainer a) {
160
+ color: var(--primaryColor);
161
+ }
162
+ :global(.EasyMDEContainer a:hover) {
163
+ color: var(--primaryColorHover);
164
+ }
165
+ /* Allow full screen offset */
166
+ :global(.EasyMDEContainer .editor-toolbar.fullscreen) {
167
+ left: var(--fullscreen-offset-x);
168
+ top: var(--fullscreen-offset-y);
169
+ }
170
+ :global(.EasyMDEContainer .CodeMirror-fullscreen) {
171
+ left: var(--fullscreen-offset-x);
172
+ top: calc(50px + var(--fullscreen-offset-y));
173
+ }
174
+
175
+ :global(.EasyMDEContainer .CodeMirror-fullscreen.CodeMirror-sided) {
176
+ width: calc((100% - var(--fullscreen-offset-x)) / 2) !important;
177
+ }
178
+
179
+ :global(.EasyMDEContainer .editor-preview-side) {
180
+ left: calc(50% + (var(--fullscreen-offset-x) / 2));
181
+ top: calc(50px + var(--fullscreen-offset-y));
182
+ width: calc((100% - var(--fullscreen-offset-x)) / 2) !important;
183
+ }
184
+ </style>
@@ -1,7 +1,12 @@
1
1
  <script>
2
+ import { createEventDispatcher } from "svelte"
3
+
2
4
  export let type = "info"
3
5
  export let icon = "Info"
4
6
  export let message = ""
7
+ export let dismissable = false
8
+
9
+ const dispatch = createEventDispatcher()
5
10
  </script>
6
11
 
7
12
  <div class="spectrum-Toast spectrum-Toast--{type}">
@@ -17,4 +22,28 @@
17
22
  <div class="spectrum-Toast-body">
18
23
  <div class="spectrum-Toast-content">{message || ""}</div>
19
24
  </div>
25
+ {#if dismissable}
26
+ <div class="spectrum-Toast-buttons">
27
+ <button
28
+ class="spectrum-ClearButton spectrum-ClearButton--overBackground spectrum-ClearButton--sizeM"
29
+ on:click={() => dispatch("dismiss")}
30
+ >
31
+ <div class="spectrum-ClearButton-fill">
32
+ <svg
33
+ class="spectrum-ClearButton-icon spectrum-Icon spectrum-UIIcon-Cross100"
34
+ focusable="false"
35
+ aria-hidden="true"
36
+ >
37
+ <use xlink:href="#spectrum-css-icon-Cross100" />
38
+ </svg>
39
+ </div>
40
+ </button>
41
+ </div>
42
+ {/if}
20
43
  </div>
44
+
45
+ <style>
46
+ .spectrum-Toast {
47
+ pointer-events: all;
48
+ }
49
+ </style>
@@ -1,7 +1,6 @@
1
1
  <script>
2
2
  import "@spectrum-css/toast/dist/index-vars.css"
3
3
  import Portal from "svelte-portal"
4
- import { flip } from "svelte/animate"
5
4
  import { notifications } from "../Stores/notifications"
6
5
  import Notification from "./Notification.svelte"
7
6
  import { fly } from "svelte/transition"
@@ -9,9 +8,15 @@
9
8
 
10
9
  <Portal target=".modal-container">
11
10
  <div class="notifications">
12
- {#each $notifications as { type, icon, message, id } (id)}
13
- <div animate:flip transition:fly={{ y: -30 }}>
14
- <Notification {type} {icon} {message} />
11
+ {#each $notifications as { type, icon, message, id, dismissable } (id)}
12
+ <div transition:fly={{ y: -30 }}>
13
+ <Notification
14
+ {type}
15
+ {icon}
16
+ {message}
17
+ {dismissable}
18
+ on:dismiss={() => notifications.dismiss(id)}
19
+ />
15
20
  </div>
16
21
  {/each}
17
22
  </div>
@@ -20,20 +20,29 @@ export const createNotificationStore = () => {
20
20
  setTimeout(() => (block = false), timeout)
21
21
  }
22
22
 
23
- const send = (message, type = "default", icon = "") => {
23
+ const send = (message, type = "default", icon = "", autoDismiss = true) => {
24
24
  if (block) {
25
25
  return
26
26
  }
27
27
  let _id = id()
28
28
  _notifications.update(state => {
29
- return [...state, { id: _id, type, message, icon }]
29
+ return [
30
+ ...state,
31
+ { id: _id, type, message, icon, dismissable: !autoDismiss },
32
+ ]
33
+ })
34
+ if (autoDismiss) {
35
+ const timeoutId = setTimeout(() => {
36
+ dismissNotification(_id)
37
+ }, NOTIFICATION_TIMEOUT)
38
+ timeoutIds.add(timeoutId)
39
+ }
40
+ }
41
+
42
+ const dismissNotification = id => {
43
+ _notifications.update(state => {
44
+ return state.filter(n => n.id !== id)
30
45
  })
31
- const timeoutId = setTimeout(() => {
32
- _notifications.update(state => {
33
- return state.filter(({ id }) => id !== _id)
34
- })
35
- }, NOTIFICATION_TIMEOUT)
36
- timeoutIds.add(timeoutId)
37
46
  }
38
47
 
39
48
  const { subscribe } = _notifications
@@ -42,10 +51,11 @@ export const createNotificationStore = () => {
42
51
  subscribe,
43
52
  send,
44
53
  info: msg => send(msg, "info", "Info"),
45
- error: msg => send(msg, "error", "Alert"),
54
+ error: msg => send(msg, "error", "Alert", false),
46
55
  warning: msg => send(msg, "warning", "Alert"),
47
56
  success: msg => send(msg, "success", "CheckmarkCircle"),
48
57
  blockNotifications,
58
+ dismiss: dismissNotification,
49
59
  }
50
60
  }
51
61
 
@@ -8,10 +8,35 @@
8
8
  copyToClipboard(value)
9
9
  }
10
10
 
11
- function copyToClipboard(value) {
12
- navigator.clipboard.writeText(value).then(() => {
13
- notifications.success("Copied")
11
+ const copyToClipboard = value => {
12
+ return new Promise(res => {
13
+ if (navigator.clipboard && window.isSecureContext) {
14
+ // Try using the clipboard API first
15
+ navigator.clipboard.writeText(value).then(res)
16
+ } else {
17
+ // Fall back to the textarea hack
18
+ let textArea = document.createElement("textarea")
19
+ textArea.value = value
20
+ textArea.style.position = "fixed"
21
+ textArea.style.left = "-9999px"
22
+ textArea.style.top = "-9999px"
23
+ document.body.appendChild(textArea)
24
+ textArea.focus()
25
+ textArea.select()
26
+ document.execCommand("copy")
27
+ textArea.remove()
28
+ res()
29
+ }
14
30
  })
31
+ .then(() => {
32
+ notifications.success("Copied to clipboard")
33
+ })
34
+ .catch(() => {
35
+ notifications.error(
36
+ "Failed to copy to clipboard. Check the dev console for the value."
37
+ )
38
+ console.warn("Failed to copy the value", value)
39
+ })
15
40
  }
16
41
  </script>
17
42
 
package/src/index.js CHANGED
@@ -60,6 +60,9 @@ export { default as StatusLight } from "./StatusLight/StatusLight.svelte"
60
60
  export { default as ColorPicker } from "./ColorPicker/ColorPicker.svelte"
61
61
  export { default as InlineAlert } from "./InlineAlert/InlineAlert.svelte"
62
62
  export { default as Banner } from "./Banner/Banner.svelte"
63
+ export { default as MarkdownEditor } from "./Markdown/MarkdownEditor.svelte"
64
+ export { default as MarkdownViewer } from "./Markdown/MarkdownViewer.svelte"
65
+ export { default as RichTextField } from "./Form/RichTextField.svelte"
63
66
 
64
67
  // Renderers
65
68
  export { default as BoldRenderer } from "./Table/BoldRenderer.svelte"