@budibase/bbui 2.2.26 → 2.2.27

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.
Files changed (57) hide show
  1. package/dist/bbui.es.js +3 -3
  2. package/dist/bbui.es.js.map +1 -1
  3. package/package.json +4 -3
  4. package/src/Accordion/Accordion.svelte +58 -0
  5. package/src/ActionButton/ActionButton.svelte +2 -3
  6. package/src/ActionMenu/ActionMenu.svelte +1 -2
  7. package/src/Actions/click_outside.js +28 -9
  8. package/src/Actions/position_dropdown.js +85 -53
  9. package/src/Avatar/Avatar.svelte +1 -0
  10. package/src/Badge/Badge.svelte +13 -0
  11. package/src/Button/Button.svelte +8 -4
  12. package/src/Drawer/DrawerContent.svelte +0 -1
  13. package/src/FancyForm/FancyButton.svelte +30 -0
  14. package/src/FancyForm/FancyButtonRadio.svelte +70 -0
  15. package/src/FancyForm/FancyCheckbox.svelte +53 -0
  16. package/src/FancyForm/FancyField.svelte +126 -0
  17. package/src/FancyForm/FancyFieldLabel.svelte +25 -0
  18. package/src/FancyForm/FancyForm.svelte +40 -0
  19. package/src/FancyForm/FancyInput.svelte +77 -0
  20. package/src/FancyForm/FancySelect.svelte +147 -0
  21. package/src/FancyForm/index.js +6 -0
  22. package/src/Form/Core/DatePicker.svelte +1 -1
  23. package/src/Form/Core/Dropzone.svelte +71 -66
  24. package/src/Form/Core/EnvDropdown.svelte +276 -0
  25. package/src/Form/Core/Picker.svelte +141 -124
  26. package/src/Form/Core/Select.svelte +3 -0
  27. package/src/Form/Core/Switch.svelte +0 -2
  28. package/src/Form/Core/TextField.svelte +0 -6
  29. package/src/Form/EnvDropdown.svelte +50 -0
  30. package/src/Form/Input.svelte +0 -2
  31. package/src/Form/InputDropdown.svelte +0 -2
  32. package/src/Form/PickerDropdown.svelte +0 -2
  33. package/src/Form/Toggle.svelte +1 -2
  34. package/src/Icon/IconAvatar.svelte +3 -0
  35. package/src/InlineAlert/InlineAlert.svelte +1 -0
  36. package/src/Label/Label.svelte +1 -0
  37. package/src/Layout/Page.svelte +82 -13
  38. package/src/Link/Link.svelte +4 -1
  39. package/src/List/ListItem.svelte +6 -3
  40. package/src/Menu/Item.svelte +0 -2
  41. package/src/Modal/CustomContent.svelte +0 -1
  42. package/src/Modal/Modal.svench +0 -1
  43. package/src/Modal/ModalContent.svelte +4 -4
  44. package/src/Modal/QuizModal.svelte +0 -1
  45. package/src/Popover/Popover.svelte +35 -31
  46. package/src/Popover/Popover.svench +0 -1
  47. package/src/SideNavigation/Item.svelte +0 -2
  48. package/src/Table/RelationshipRenderer.svelte +3 -8
  49. package/src/Table/StringRenderer.svelte +9 -1
  50. package/src/Table/Table.svelte +30 -19
  51. package/src/Tabs/Tab.svelte +5 -5
  52. package/src/Tags/Tag.svelte +1 -1
  53. package/src/Tags/Tags.svelte +10 -0
  54. package/src/Typography/Heading.svelte +6 -0
  55. package/src/bbui.css +7 -3
  56. package/src/context.js +1 -0
  57. package/src/index.js +5 -0
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": "2.2.26",
4
+ "version": "2.2.27",
5
5
  "license": "MPL-2.0",
6
6
  "svelte": "src/index.js",
7
7
  "module": "dist/bbui.es.js",
@@ -38,7 +38,8 @@
38
38
  ],
39
39
  "dependencies": {
40
40
  "@adobe/spectrum-css-workflow-icons": "1.2.1",
41
- "@budibase/string-templates": "^2.2.26",
41
+ "@budibase/string-templates": "^2.2.27",
42
+ "@spectrum-css/accordion": "3.0.24",
42
43
  "@spectrum-css/actionbutton": "1.0.1",
43
44
  "@spectrum-css/actiongroup": "1.0.1",
44
45
  "@spectrum-css/avatar": "3.0.2",
@@ -88,5 +89,5 @@
88
89
  "resolutions": {
89
90
  "loader-utils": "1.4.1"
90
91
  },
91
- "gitHead": "a60084bc0ef21373d57e3d0d75872cdc2b3af5b4"
92
+ "gitHead": "d5e24cc25f7eff877eb808046f0069359fddeb74"
92
93
  }
@@ -0,0 +1,58 @@
1
+ <script>
2
+ import "@spectrum-css/accordion"
3
+
4
+ export let itemName
5
+ export let initialOpen
6
+ export let header
7
+
8
+ let isOpen
9
+
10
+ function getOpenClass(isOpen) {
11
+ if (isOpen === undefined) {
12
+ isOpen = initialOpen
13
+ }
14
+ return isOpen ? "is-open" : ""
15
+ }
16
+ </script>
17
+
18
+ <div class="spectrum-Accordion" role={itemName}>
19
+ <div class="spectrum-Accordion-item {getOpenClass(isOpen)}">
20
+ <h3 class="spectrum-Accordion-itemHeading">
21
+ <button
22
+ class="spectrum-Accordion-itemHeader"
23
+ type="button"
24
+ on:click={() => (isOpen = !isOpen)}
25
+ >
26
+ {header}
27
+ </button>
28
+ <svg
29
+ class="spectrum-Icon spectrum-UIIcon-ChevronRight100 spectrum-Accordion-itemIndicator"
30
+ focusable="false"
31
+ aria-hidden="true"
32
+ >
33
+ <use xlink:href="#spectrum-css-icon-Chevron100" />
34
+ </svg>
35
+ </h3>
36
+ <div class="spectrum-Accordion-itemContent" role={itemName}>
37
+ <slot />
38
+ </div>
39
+ </div>
40
+ </div>
41
+
42
+ <style>
43
+ .spectrum-Accordion {
44
+ margin-left: -20px;
45
+ }
46
+ .spectrum-Accordion-item {
47
+ border: none;
48
+ }
49
+ .spectrum-Accordion-itemContent {
50
+ width: 97%;
51
+ padding-left: 30px;
52
+ }
53
+ .spectrum-Accordion-itemHeader {
54
+ text-transform: none;
55
+ font-weight: bold;
56
+ font-size: 0.875rem;
57
+ }
58
+ </style>
@@ -9,7 +9,6 @@
9
9
  export let longPressable = false
10
10
  export let disabled = false
11
11
  export let icon = ""
12
- export let dataCy = null
13
12
  export let size = "M"
14
13
  export let active = false
15
14
  export let fullWidth = false
@@ -37,7 +36,6 @@
37
36
  </script>
38
37
 
39
38
  <button
40
- data-cy={dataCy}
41
39
  use:longPress
42
40
  class:spectrum-ActionButton--quiet={quiet}
43
41
  class:spectrum-ActionButton--emphasized={emphasized}
@@ -86,8 +84,9 @@
86
84
  margin-left: 0;
87
85
  transition: color ease-out 130ms;
88
86
  }
89
- .is-selected:not(.spectrum-ActionButton--emphasized) {
87
+ .is-selected:not(.spectrum-ActionButton--emphasized):not(.spectrum-ActionButton--quiet) {
90
88
  background: var(--spectrum-global-color-gray-300);
89
+ border-color: var(--spectrum-global-color-gray-500);
91
90
  }
92
91
  .noPadding {
93
92
  padding: 0;
@@ -6,7 +6,6 @@
6
6
  export let disabled = false
7
7
  export let align = "left"
8
8
  export let portalTarget
9
- export let dataCy
10
9
 
11
10
  let anchor
12
11
  let dropdown
@@ -37,7 +36,7 @@
37
36
  <div use:getAnchor on:click={openMenu}>
38
37
  <slot name="control" />
39
38
  </div>
40
- <Popover bind:this={dropdown} {anchor} {align} {portalTarget} {dataCy}>
39
+ <Popover bind:this={dropdown} {anchor} {align} {portalTarget}>
41
40
  <Menu>
42
41
  <slot />
43
42
  </Menu>
@@ -1,11 +1,11 @@
1
- const ignoredClasses = [".flatpickr-calendar", ".modal-container"]
1
+ const ignoredClasses = [".flatpickr-calendar", ".spectrum-Popover"]
2
2
  let clickHandlers = []
3
3
 
4
4
  /**
5
5
  * Handle a body click event
6
6
  */
7
7
  const handleClick = event => {
8
- // Ignore click if needed
8
+ // Ignore click if this is an ignored class
9
9
  for (let className of ignoredClasses) {
10
10
  if (event.target.closest(className)) {
11
11
  return
@@ -14,9 +14,18 @@ const handleClick = event => {
14
14
 
15
15
  // Process handlers
16
16
  clickHandlers.forEach(handler => {
17
- if (!handler.element.contains(event.target)) {
18
- handler.callback?.(event)
17
+ if (handler.element.contains(event.target)) {
18
+ return
19
+ }
20
+
21
+ // Ignore clicks for modals, unless the handler is registered from a modal
22
+ const sourceInModal = handler.anchor.closest(".spectrum-Modal") != null
23
+ const clickInModal = event.target.closest(".spectrum-Modal") != null
24
+ if (clickInModal && !sourceInModal) {
25
+ return
19
26
  }
27
+
28
+ handler.callback?.(event)
20
29
  })
21
30
  }
22
31
  document.documentElement.addEventListener("click", handleClick, true)
@@ -24,10 +33,10 @@ document.documentElement.addEventListener("click", handleClick, true)
24
33
  /**
25
34
  * Adds or updates a click handler
26
35
  */
27
- const updateHandler = (id, element, callback) => {
36
+ const updateHandler = (id, element, anchor, callback) => {
28
37
  let existingHandler = clickHandlers.find(x => x.id === id)
29
38
  if (!existingHandler) {
30
- clickHandlers.push({ id, element, callback })
39
+ clickHandlers.push({ id, element, anchor, callback })
31
40
  } else {
32
41
  existingHandler.callback = callback
33
42
  }
@@ -42,12 +51,22 @@ const removeHandler = id => {
42
51
 
43
52
  /**
44
53
  * Svelte action to apply a click outside handler for a certain element
54
+ * opts.anchor is an optional param specifying the real root source of the
55
+ * component being observed. This is required for things like popovers, where
56
+ * the element using the clickoutside action is the popover, but the popover is
57
+ * rendered at the root of the DOM somewhere, whereas the popover anchor is the
58
+ * element we actually want to consider when determining the source component.
45
59
  */
46
- export default (element, callback) => {
60
+ export default (element, opts) => {
47
61
  const id = Math.random()
48
- updateHandler(id, element, callback)
62
+ const update = newOpts => {
63
+ const callback = newOpts?.callback || newOpts
64
+ const anchor = newOpts?.anchor || element
65
+ updateHandler(id, element, anchor, callback)
66
+ }
67
+ update(opts)
49
68
  return {
50
- update: newCallback => updateHandler(id, element, newCallback),
69
+ update,
51
70
  destroy: () => removeHandler(id),
52
71
  }
53
72
  }
@@ -1,75 +1,107 @@
1
- export default function positionDropdown(element, { anchor, align, maxWidth }) {
2
- let positionSide = "top"
3
- let maxHeight = 0
4
- let dimensions = getDimensions(anchor)
1
+ export default function positionDropdown(element, opts) {
2
+ let resizeObserver
3
+ let latestOpts = opts
5
4
 
6
- function getDimensions() {
7
- const {
8
- bottom,
9
- top: spaceAbove,
10
- left,
11
- width,
12
- } = anchor.getBoundingClientRect()
13
- const spaceBelow = window.innerHeight - bottom
14
- const containerRect = element.getBoundingClientRect()
5
+ // We need a static reference to this function so that we can properly
6
+ // clean up the scroll listener.
7
+ const scrollUpdate = () => {
8
+ updatePosition(latestOpts)
9
+ }
15
10
 
16
- let y
11
+ // Updates the position of the dropdown
12
+ const updatePosition = opts => {
13
+ const { anchor, align, maxWidth, useAnchorWidth, offset = 5 } = opts
14
+ if (!anchor) {
15
+ return
16
+ }
17
17
 
18
- if (spaceAbove > spaceBelow) {
19
- positionSide = "bottom"
20
- maxHeight = spaceAbove - 20
21
- y = window.innerHeight - spaceAbove + 5
18
+ // Compute bounds
19
+ const anchorBounds = anchor.getBoundingClientRect()
20
+ const elementBounds = element.getBoundingClientRect()
21
+ let styles = {
22
+ maxHeight: null,
23
+ minWidth: null,
24
+ maxWidth,
25
+ left: null,
26
+ top: null,
27
+ }
28
+
29
+ // Determine vertical styles
30
+ if (align === "right-outside") {
31
+ styles.top = anchorBounds.top
32
+ } else if (window.innerHeight - anchorBounds.bottom < 100) {
33
+ styles.top = anchorBounds.top - elementBounds.height - offset
22
34
  } else {
23
- positionSide = "top"
24
- y = bottom + 5
25
- maxHeight = spaceBelow - 20
35
+ styles.top = anchorBounds.bottom + offset
36
+ styles.maxHeight = window.innerHeight - anchorBounds.bottom - 20
26
37
  }
27
38
 
28
- return {
29
- [positionSide]: y,
30
- left,
31
- width,
32
- containerWidth: containerRect.width,
39
+ // Determine horizontal styles
40
+ if (!maxWidth && useAnchorWidth) {
41
+ styles.maxWidth = anchorBounds.width
42
+ }
43
+ if (useAnchorWidth) {
44
+ styles.minWidth = anchorBounds.width
33
45
  }
46
+ if (align === "right") {
47
+ styles.left = anchorBounds.left + anchorBounds.width - elementBounds.width
48
+ } else if (align === "right-outside") {
49
+ styles.left = anchorBounds.right + offset
50
+ } else {
51
+ styles.left = anchorBounds.left
52
+ }
53
+
54
+ // Apply styles
55
+ Object.entries(styles).forEach(([style, value]) => {
56
+ if (value) {
57
+ element.style[style] = `${value.toFixed(0)}px`
58
+ } else {
59
+ element.style[style] = null
60
+ }
61
+ })
34
62
  }
35
63
 
36
- function calcLeftPosition() {
37
- let left
64
+ // The actual svelte action callback which creates observers on the relevant
65
+ // DOM elements
66
+ const update = newOpts => {
67
+ latestOpts = newOpts
38
68
 
39
- if (align == "right") {
40
- left = dimensions.left + dimensions.width - dimensions.containerWidth
41
- } else if (align == "right-side") {
42
- left = dimensions.left + dimensions.width
43
- } else {
44
- left = dimensions.left
69
+ // Cleanup old state
70
+ if (resizeObserver) {
71
+ resizeObserver.disconnect()
45
72
  }
46
73
 
47
- return left
74
+ // Do nothing if no anchor
75
+ const { anchor } = newOpts
76
+ if (!anchor) {
77
+ return
78
+ }
79
+
80
+ // Observe both anchor and element and resize the popover as appropriate
81
+ resizeObserver = new ResizeObserver(() => updatePosition(newOpts))
82
+ resizeObserver.observe(anchor)
83
+ resizeObserver.observe(element)
84
+ resizeObserver.observe(document.body)
48
85
  }
49
86
 
87
+ // Apply initial styles which don't need to change
50
88
  element.style.position = "absolute"
51
89
  element.style.zIndex = "9999"
52
- if (maxWidth) {
53
- element.style.maxWidth = `${maxWidth}px`
54
- }
55
- element.style.minWidth = `${dimensions.width}px`
56
- element.style.maxHeight = `${maxHeight.toFixed(0)}px`
57
- element.style.transformOrigin = `center ${positionSide}`
58
- element.style[positionSide] = `${dimensions[positionSide]}px`
59
- element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px`
60
90
 
61
- const resizeObserver = new ResizeObserver(entries => {
62
- entries.forEach(() => {
63
- dimensions = getDimensions()
64
- element.style[positionSide] = `${dimensions[positionSide]}px`
65
- element.style.left = `${calcLeftPosition(dimensions).toFixed(0)}px`
66
- })
67
- })
68
- resizeObserver.observe(anchor)
69
- resizeObserver.observe(element)
91
+ // Set up a scroll listener
92
+ document.addEventListener("scroll", scrollUpdate, true)
93
+
94
+ // Perform initial update
95
+ update(opts)
96
+
70
97
  return {
98
+ update,
71
99
  destroy() {
72
- resizeObserver.disconnect()
100
+ // Cleanup
101
+ if (resizeObserver) {
102
+ resizeObserver.disconnect()
103
+ }
104
+ document.removeEventListener("scroll", scrollUpdate, true)
73
105
  },
74
106
  }
75
107
  }
@@ -58,5 +58,6 @@
58
58
  overflow: hidden;
59
59
  user-select: none;
60
60
  text-transform: uppercase;
61
+ flex-shrink: 0;
61
62
  }
62
63
  </style>
@@ -10,10 +10,13 @@
10
10
  export let green = false
11
11
  export let active = false
12
12
  export let inactive = false
13
+ export let hoverable = false
13
14
  </script>
14
15
 
15
16
  <span
17
+ on:click
16
18
  class="spectrum-Label"
19
+ class:hoverable
17
20
  class:spectrum-Label--small={size === "S"}
18
21
  class:spectrum-Label--large={size === "L"}
19
22
  class:spectrum-Label--grey={grey}
@@ -27,3 +30,13 @@
27
30
  >
28
31
  <slot />
29
32
  </span>
33
+
34
+ <style>
35
+ .spectrum-Label--grey {
36
+ background-color: var(--spectrum-global-color-gray-500);
37
+ font-weight: 600;
38
+ }
39
+ .hoverable:hover {
40
+ cursor: pointer;
41
+ }
42
+ </style>
@@ -13,13 +13,14 @@
13
13
  export let icon = undefined
14
14
  export let active = false
15
15
  export let tooltip = undefined
16
- export let dataCy
17
- export let newStyles = false
16
+ export let newStyles = true
17
+ export let id
18
18
 
19
19
  let showTooltip = false
20
20
  </script>
21
21
 
22
22
  <button
23
+ {id}
23
24
  class:spectrum-Button--cta={cta}
24
25
  class:spectrum-Button--primary={primary}
25
26
  class:spectrum-Button--secondary={secondary}
@@ -28,9 +29,9 @@
28
29
  class:spectrum-Button--quiet={quiet}
29
30
  class:new-styles={newStyles}
30
31
  class:active
32
+ class:disabled
31
33
  class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
32
34
  {disabled}
33
- data-cy={dataCy}
34
35
  on:click|preventDefault
35
36
  on:mouseover={() => (showTooltip = true)}
36
37
  on:focus={() => (showTooltip = true)}
@@ -108,7 +109,10 @@
108
109
  border-color: transparent;
109
110
  color: var(--spectrum-global-color-gray-900);
110
111
  }
111
- .spectrum-Button--secondary.new-styles:hover {
112
+ .spectrum-Button--secondary.new-styles:not(.disabled):hover {
112
113
  background: var(--spectrum-global-color-gray-300);
113
114
  }
115
+ .spectrum-Button--secondary.new-styles.disabled {
116
+ color: var(--spectrum-global-color-gray-500);
117
+ }
114
118
  </style>
@@ -34,7 +34,6 @@
34
34
  display: none;
35
35
  }
36
36
  .main {
37
- font-family: var(--font-sans);
38
37
  padding: var(--spacing-xl);
39
38
  }
40
39
  .main :global(textarea) {
@@ -0,0 +1,30 @@
1
+ <script>
2
+ import Icon from "../Icon/Icon.svelte"
3
+ import FancyField from "./FancyField.svelte"
4
+
5
+ export let icon
6
+ export let disabled
7
+ </script>
8
+
9
+ <FancyField on:click clickable {disabled}>
10
+ {#if icon}
11
+ {#if icon.includes("/")}
12
+ <img src={icon} alt="button" />
13
+ {:else}
14
+ <Icon name={icon} />
15
+ {/if}
16
+ {/if}
17
+ <slot name="icon" />
18
+ <div>
19
+ <slot />
20
+ </div>
21
+ </FancyField>
22
+
23
+ <style>
24
+ img {
25
+ width: 22px;
26
+ }
27
+ div {
28
+ font-size: var(--font-size-l);
29
+ }
30
+ </style>
@@ -0,0 +1,70 @@
1
+ <script>
2
+ import { createEventDispatcher } from "svelte"
3
+ import FancyField from "./FancyField.svelte"
4
+ import FancyFieldLabel from "./FancyFieldLabel.svelte"
5
+ import ActionButton from "../ActionButton/ActionButton.svelte"
6
+
7
+ export let label
8
+ export let value
9
+ export let disabled = false
10
+ export let error = null
11
+ export let validate = null
12
+ export let options = []
13
+ export let getOptionLabel = option => extractProperty(option, "label")
14
+ export let getOptionValue = option => extractProperty(option, "value")
15
+
16
+ const dispatch = createEventDispatcher()
17
+
18
+ $: placeholder = !value
19
+
20
+ const extractProperty = (value, property) => {
21
+ if (value && typeof value === "object") {
22
+ return value[property]
23
+ }
24
+ return value
25
+ }
26
+
27
+ const onChange = newValue => {
28
+ dispatch("change", newValue)
29
+ value = newValue
30
+ if (validate) {
31
+ error = validate(newValue)
32
+ }
33
+ }
34
+ </script>
35
+
36
+ <FancyField {error} {value} {validate} {disabled} autoHeight>
37
+ {#if label}
38
+ <FancyFieldLabel placeholder={false}>{label}</FancyFieldLabel>
39
+ {/if}
40
+
41
+ <div class="options">
42
+ {#each options as option}
43
+ <ActionButton
44
+ selected={getOptionValue(option) === value}
45
+ on:click={() => onChange(getOptionValue(option))}
46
+ >
47
+ {getOptionLabel(option)}
48
+ </ActionButton>
49
+ {/each}
50
+ </div>
51
+ </FancyField>
52
+
53
+ <style>
54
+ .options {
55
+ margin-top: 34px;
56
+ margin-bottom: 14px;
57
+ display: flex;
58
+ flex-direction: row;
59
+ justify-content: flex-start;
60
+ align-items: center;
61
+ gap: 6px;
62
+ flex-wrap: wrap;
63
+ }
64
+ .options :global(.spectrum-ActionButton) {
65
+ font-size: 15px;
66
+ line-height: 17px;
67
+ height: auto;
68
+ padding: 6px 10px;
69
+ }
70
+ </style>
@@ -0,0 +1,53 @@
1
+ <script>
2
+ import { createEventDispatcher } from "svelte"
3
+ import FancyField from "./FancyField.svelte"
4
+ import Checkbox from "../Form/Core/Checkbox.svelte"
5
+
6
+ export let value
7
+ export let text
8
+ export let disabled = false
9
+ export let error = null
10
+ export let validate = null
11
+
12
+ const dispatch = createEventDispatcher()
13
+
14
+ const onChange = () => {
15
+ const newValue = !value
16
+ dispatch("change", newValue)
17
+ value = newValue
18
+ if (validate) {
19
+ error = validate(newValue)
20
+ }
21
+ }
22
+ </script>
23
+
24
+ <FancyField {error} {value} {validate} {disabled} clickable on:click={onChange}>
25
+ <span>
26
+ <Checkbox {disabled} {value} />
27
+ </span>
28
+ <div class="text">
29
+ {#if text}
30
+ {text}
31
+ {/if}
32
+ <slot />
33
+ </div>
34
+ </FancyField>
35
+
36
+ <style>
37
+ span {
38
+ pointer-events: none;
39
+ }
40
+ .text {
41
+ font-size: 15px;
42
+ line-height: 17px;
43
+ overflow: hidden;
44
+ text-overflow: ellipsis;
45
+ display: -webkit-box;
46
+ -webkit-line-clamp: 2;
47
+ line-clamp: 2;
48
+ -webkit-box-orient: vertical;
49
+ }
50
+ .text > :global(*) {
51
+ font-size: inherit !important;
52
+ }
53
+ </style>