@budibase/bbui 2.2.12-alpha.4 → 2.2.12-alpha.40

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 (44) 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 +1 -0
  6. package/src/Actions/click_outside.js +13 -4
  7. package/src/Actions/position_dropdown.js +45 -52
  8. package/src/Avatar/Avatar.svelte +1 -0
  9. package/src/Button/Button.svelte +6 -2
  10. package/src/Drawer/DrawerContent.svelte +0 -1
  11. package/src/FancyForm/FancyButton.svelte +29 -0
  12. package/src/FancyForm/FancyButtonRadio.svelte +70 -0
  13. package/src/FancyForm/FancyCheckbox.svelte +53 -0
  14. package/src/FancyForm/FancyField.svelte +126 -0
  15. package/src/FancyForm/FancyFieldLabel.svelte +25 -0
  16. package/src/FancyForm/FancyForm.svelte +40 -0
  17. package/src/FancyForm/FancyInput.svelte +77 -0
  18. package/src/FancyForm/FancySelect.svelte +147 -0
  19. package/src/FancyForm/index.js +6 -0
  20. package/src/Form/Core/DatePicker.svelte +1 -1
  21. package/src/Form/Core/Picker.svelte +141 -124
  22. package/src/Form/Core/Select.svelte +3 -0
  23. package/src/Form/Core/TextField.svelte +0 -4
  24. package/src/Icon/IconAvatar.svelte +3 -0
  25. package/src/InlineAlert/InlineAlert.svelte +1 -0
  26. package/src/Label/Label.svelte +1 -0
  27. package/src/Layout/Page.svelte +82 -13
  28. package/src/Link/Link.svelte +4 -1
  29. package/src/List/ListItem.svelte +6 -3
  30. package/src/Modal/CustomContent.svelte +0 -1
  31. package/src/Modal/Modal.svench +0 -1
  32. package/src/Modal/ModalContent.svelte +3 -2
  33. package/src/Modal/QuizModal.svelte +0 -1
  34. package/src/Popover/Popover.svelte +21 -6
  35. package/src/Popover/Popover.svench +0 -1
  36. package/src/Table/StringRenderer.svelte +9 -1
  37. package/src/Table/Table.svelte +30 -19
  38. package/src/Tabs/Tab.svelte +2 -1
  39. package/src/Tags/Tag.svelte +1 -1
  40. package/src/Tags/Tags.svelte +10 -0
  41. package/src/Typography/Heading.svelte +6 -0
  42. package/src/bbui.css +7 -3
  43. package/src/context.js +1 -0
  44. package/src/index.js +4 -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.12-alpha.4",
4
+ "version": "2.2.12-alpha.40",
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.12-alpha.4",
41
+ "@budibase/string-templates": "2.2.12-alpha.40",
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": "90b3aaddb3f131078b05acf21651c8535b9c7d03"
92
+ "gitHead": "362a03b4de1f26f867c800a9afc8721d10d5ca89"
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>
@@ -88,6 +88,7 @@
88
88
  }
89
89
  .is-selected:not(.spectrum-ActionButton--emphasized) {
90
90
  background: var(--spectrum-global-color-gray-300);
91
+ border-color: var(--spectrum-global-color-gray-700);
91
92
  }
92
93
  .noPadding {
93
94
  padding: 0;
@@ -1,11 +1,11 @@
1
- const ignoredClasses = [".flatpickr-calendar", ".modal-container"]
1
+ const ignoredClasses = [".flatpickr-calendar"]
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
19
  }
20
+
21
+ // Ignore clicks for modals, unless the handler is registered from a modal
22
+ const sourceInModal = handler.element.closest(".spectrum-Modal") != null
23
+ const clickInModal = event.target.closest(".spectrum-Modal") != null
24
+ if (clickInModal && !sourceInModal) {
25
+ return
26
+ }
27
+
28
+ handler.callback?.(event)
20
29
  })
21
30
  }
22
31
  document.documentElement.addEventListener("click", handleClick, true)
@@ -1,75 +1,68 @@
1
- export default function positionDropdown(element, { anchor, align, maxWidth }) {
2
- let positionSide = "top"
3
- let maxHeight = 0
4
- let dimensions = getDimensions(anchor)
5
-
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()
15
-
16
- let y
1
+ export default function positionDropdown(
2
+ element,
3
+ { anchor, align, maxWidth, useAnchorWidth }
4
+ ) {
5
+ const update = () => {
6
+ const anchorBounds = anchor.getBoundingClientRect()
7
+ const elementBounds = element.getBoundingClientRect()
8
+ let styles = {
9
+ maxHeight: null,
10
+ minWidth: null,
11
+ maxWidth,
12
+ left: null,
13
+ top: null,
14
+ }
17
15
 
18
- if (spaceAbove > spaceBelow) {
19
- positionSide = "bottom"
20
- maxHeight = spaceAbove - 20
21
- y = window.innerHeight - spaceAbove + 5
16
+ // Determine vertical styles
17
+ if (window.innerHeight - anchorBounds.bottom < 100) {
18
+ styles.top = anchorBounds.top - elementBounds.height - 5
22
19
  } else {
23
- positionSide = "top"
24
- y = bottom + 5
25
- maxHeight = spaceBelow - 20
20
+ styles.top = anchorBounds.bottom + 5
21
+ styles.maxHeight = window.innerHeight - anchorBounds.bottom - 20
26
22
  }
27
23
 
28
- return {
29
- [positionSide]: y,
30
- left,
31
- width,
32
- containerWidth: containerRect.width,
24
+ // Determine horizontal styles
25
+ if (!maxWidth && useAnchorWidth) {
26
+ styles.maxWidth = anchorBounds.width
33
27
  }
34
- }
35
-
36
- function calcLeftPosition() {
37
- let left
38
-
39
- if (align == "right") {
40
- left = dimensions.left + dimensions.width - dimensions.containerWidth
41
- } else if (align == "right-side") {
42
- left = dimensions.left + dimensions.width
28
+ if (useAnchorWidth) {
29
+ styles.minWidth = anchorBounds.width
30
+ }
31
+ if (align === "right") {
32
+ styles.left = anchorBounds.left + anchorBounds.width - elementBounds.width
33
+ } else if (align === "right-side") {
34
+ styles.left = anchorBounds.left + anchorBounds.width
43
35
  } else {
44
- left = dimensions.left
36
+ styles.left = anchorBounds.left
45
37
  }
46
38
 
47
- return left
39
+ // Apply styles
40
+ Object.entries(styles).forEach(([style, value]) => {
41
+ if (value) {
42
+ element.style[style] = `${value.toFixed(0)}px`
43
+ } else {
44
+ element.style[style] = null
45
+ }
46
+ })
48
47
  }
49
48
 
49
+ // Apply initial styles which don't need to change
50
50
  element.style.position = "absolute"
51
51
  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
52
 
53
+ // Observe both anchor and element and resize the popover as appropriate
61
54
  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
- })
55
+ entries.forEach(update)
67
56
  })
68
57
  resizeObserver.observe(anchor)
69
58
  resizeObserver.observe(element)
59
+
60
+ document.addEventListener("scroll", update, true)
61
+
70
62
  return {
71
63
  destroy() {
72
64
  resizeObserver.disconnect()
65
+ document.removeEventListener("scroll", update, true)
73
66
  },
74
67
  }
75
68
  }
@@ -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>
@@ -14,7 +14,7 @@
14
14
  export let active = false
15
15
  export let tooltip = undefined
16
16
  export let dataCy
17
- export let newStyles = false
17
+ export let newStyles = true
18
18
 
19
19
  let showTooltip = false
20
20
  </script>
@@ -28,6 +28,7 @@
28
28
  class:spectrum-Button--quiet={quiet}
29
29
  class:new-styles={newStyles}
30
30
  class:active
31
+ class:disabled
31
32
  class="spectrum-Button spectrum-Button--size{size.toUpperCase()}"
32
33
  {disabled}
33
34
  data-cy={dataCy}
@@ -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,29 @@
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
+ <div>
18
+ <slot />
19
+ </div>
20
+ </FancyField>
21
+
22
+ <style>
23
+ img {
24
+ width: 22px;
25
+ }
26
+ div {
27
+ font-size: var(--font-size-l);
28
+ }
29
+ </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>
@@ -0,0 +1,126 @@
1
+ <script>
2
+ import Icon from "../Icon/Icon.svelte"
3
+ import { getContext, onMount } from "svelte"
4
+ import { slide } from "svelte/transition"
5
+
6
+ export let disabled = false
7
+ export let error = null
8
+ export let focused = false
9
+ export let clickable = false
10
+ export let validate
11
+ export let value
12
+ export let ref
13
+ export let autoHeight
14
+
15
+ const formContext = getContext("fancy-form")
16
+ const id = Math.random()
17
+ const API = {
18
+ validate: () => {
19
+ if (validate) {
20
+ error = validate(value)
21
+ }
22
+ return !error
23
+ },
24
+ }
25
+
26
+ onMount(() => {
27
+ if (formContext) {
28
+ formContext.registerField(id, API)
29
+ }
30
+ return () => {
31
+ if (formContext) {
32
+ formContext.unregisterField(id)
33
+ }
34
+ }
35
+ })
36
+ </script>
37
+
38
+ <div
39
+ bind:this={ref}
40
+ class="fancy-field"
41
+ class:error
42
+ class:disabled
43
+ class:focused
44
+ class:clickable
45
+ class:auto-height={autoHeight}
46
+ >
47
+ <div class="content" on:click>
48
+ <div class="field">
49
+ <slot />
50
+ </div>
51
+ {#if error}
52
+ <div class="error-icon">
53
+ <Icon name="Alert" />
54
+ </div>
55
+ {/if}
56
+ </div>
57
+ {#if error}
58
+ <div transition:slide|local={{ duration: 130 }} class="error-message">
59
+ {error}
60
+ </div>
61
+ {/if}
62
+ </div>
63
+
64
+ <style>
65
+ .fancy-field {
66
+ max-width: 400px;
67
+ background: var(--spectrum-global-color-gray-75);
68
+ border: 1px solid var(--spectrum-global-color-gray-300);
69
+ border-radius: 4px;
70
+ box-sizing: border-box;
71
+ transition: border-color 130ms ease-out, background 130ms ease-out,
72
+ background 130ms ease-out;
73
+ color: var(--spectrum-global-color-gray-800);
74
+ }
75
+ .fancy-field:hover {
76
+ border-color: var(--spectrum-global-color-gray-400);
77
+ }
78
+ .fancy-field.clickable:hover {
79
+ background: var(--spectrum-global-color-gray-200);
80
+ cursor: pointer;
81
+ }
82
+ .fancy-field.focused {
83
+ border-color: var(--spectrum-global-color-blue-400);
84
+ }
85
+ .fancy-field.error {
86
+ border-color: var(--spectrum-global-color-red-400);
87
+ }
88
+ .fancy-field.disabled {
89
+ background: var(--spectrum-global-color-gray-200);
90
+ color: var(--spectrum-global-color-gray-400);
91
+ border: 1px solid var(--spectrum-global-color-gray-300);
92
+ pointer-events: none;
93
+ }
94
+ .content {
95
+ position: relative;
96
+ height: 64px;
97
+ padding: 0 16px;
98
+ }
99
+ .fancy-field.auto-height .content {
100
+ height: auto;
101
+ }
102
+ .content,
103
+ .field {
104
+ display: flex;
105
+ flex-direction: row;
106
+ justify-content: flex-start;
107
+ align-items: center;
108
+ gap: 16px;
109
+ }
110
+ .field {
111
+ flex: 1 1 auto;
112
+ }
113
+ .error-message {
114
+ background: var(--spectrum-global-color-red-400);
115
+ color: white;
116
+ font-size: 14px;
117
+ padding: 6px 16px;
118
+ font-weight: 500;
119
+ }
120
+ .error-icon {
121
+ flex: 0 0 auto;
122
+ }
123
+ .error-icon :global(.spectrum-Icon) {
124
+ fill: var(--spectrum-global-color-red-400);
125
+ }
126
+ </style>
@@ -0,0 +1,25 @@
1
+ <script>
2
+ export let placeholder = true
3
+ </script>
4
+
5
+ <div class:placeholder>
6
+ <slot />
7
+ </div>
8
+
9
+ <style>
10
+ div {
11
+ font-size: 14px;
12
+ line-height: 15px;
13
+ font-weight: 500;
14
+ position: absolute;
15
+ top: 10px;
16
+ color: var(--spectrum-global-color-gray-600);
17
+ transition: font-size 130ms ease-out, top 130ms ease-out,
18
+ transform 130ms ease-out;
19
+ }
20
+ div.placeholder {
21
+ top: 50%;
22
+ font-size: 15px;
23
+ transform: translateY(-50%);
24
+ }
25
+ </style>
@@ -0,0 +1,40 @@
1
+ <script>
2
+ import { setContext } from "svelte"
3
+
4
+ let fields = {}
5
+
6
+ setContext("fancy-form", {
7
+ registerField: (id, api) => {
8
+ fields = { ...fields, [id]: api }
9
+ },
10
+ unregisterField: id => {
11
+ delete fields[id]
12
+ fields = fields
13
+ },
14
+ })
15
+
16
+ export const validate = () => {
17
+ let valid = true
18
+ Object.values(fields).forEach(api => {
19
+ if (!api.validate()) {
20
+ valid = false
21
+ }
22
+ })
23
+ return valid
24
+ }
25
+ </script>
26
+
27
+ <div class="fancy-form">
28
+ <slot />
29
+ </div>
30
+
31
+ <style>
32
+ .fancy-form :global(.fancy-field:not(:first-of-type)) {
33
+ border-top-left-radius: 0;
34
+ border-top-right-radius: 0;
35
+ }
36
+ .fancy-form :global(.fancy-field:not(:last-of-type)) {
37
+ border-bottom-left-radius: 0;
38
+ border-bottom-right-radius: 0;
39
+ }
40
+ </style>