@budibase/frontend-core 2.5.6-alpha.4 → 2.5.6-alpha.41

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 (67) hide show
  1. package/package.json +6 -4
  2. package/src/api/other.js +0 -9
  3. package/src/components/grid/cells/AttachmentCell.svelte +154 -0
  4. package/src/components/grid/cells/BooleanCell.svelte +44 -0
  5. package/src/components/grid/cells/DataCell.svelte +91 -0
  6. package/src/components/grid/cells/DateCell.svelte +77 -0
  7. package/src/components/grid/cells/FormulaCell.svelte +5 -0
  8. package/src/components/grid/cells/GridCell.svelte +165 -0
  9. package/src/components/grid/cells/GutterCell.svelte +127 -0
  10. package/src/components/grid/cells/HeaderCell.svelte +248 -0
  11. package/src/components/grid/cells/JSONCell.svelte +36 -0
  12. package/src/components/grid/cells/LongFormCell.svelte +122 -0
  13. package/src/components/grid/cells/MultiSelectCell.svelte +7 -0
  14. package/src/components/grid/cells/NumberCell.svelte +14 -0
  15. package/src/components/grid/cells/OptionsCell.svelte +249 -0
  16. package/src/components/grid/cells/RelationshipCell.svelte +518 -0
  17. package/src/components/grid/cells/TextCell.svelte +111 -0
  18. package/src/components/grid/controls/AddColumnButton.svelte +16 -0
  19. package/src/components/grid/controls/AddRowButton.svelte +18 -0
  20. package/src/components/grid/controls/BetaButton.svelte +46 -0
  21. package/src/components/grid/controls/ColumnWidthButton.svelte +91 -0
  22. package/src/components/grid/controls/DeleteButton.svelte +66 -0
  23. package/src/components/grid/controls/HideColumnsButton.svelte +95 -0
  24. package/src/components/grid/controls/RowHeightButton.svelte +70 -0
  25. package/src/components/grid/controls/SortButton.svelte +129 -0
  26. package/src/components/grid/index.js +1 -0
  27. package/src/components/grid/layout/Avatar.svelte +24 -0
  28. package/src/components/grid/layout/Grid.svelte +254 -0
  29. package/src/components/grid/layout/GridBody.svelte +73 -0
  30. package/src/components/grid/layout/GridRow.svelte +59 -0
  31. package/src/components/grid/layout/GridScrollWrapper.svelte +81 -0
  32. package/src/components/grid/layout/HeaderRow.svelte +62 -0
  33. package/src/components/grid/layout/KeyboardShortcut.svelte +53 -0
  34. package/src/components/grid/layout/NewRow.svelte +334 -0
  35. package/src/components/grid/layout/RowCount.svelte +9 -0
  36. package/src/components/grid/layout/StickyColumn.svelte +176 -0
  37. package/src/components/grid/layout/UserAvatars.svelte +20 -0
  38. package/src/components/grid/lib/constants.js +14 -0
  39. package/src/components/grid/lib/events.js +29 -0
  40. package/src/components/grid/lib/renderers.js +29 -0
  41. package/src/components/grid/lib/utils.js +26 -0
  42. package/src/components/grid/lib/websocket.js +63 -0
  43. package/src/components/grid/overlays/KeyboardManager.svelte +232 -0
  44. package/src/components/grid/overlays/MenuOverlay.svelte +96 -0
  45. package/src/components/grid/overlays/ReorderOverlay.svelte +63 -0
  46. package/src/components/grid/overlays/ResizeOverlay.svelte +70 -0
  47. package/src/components/grid/overlays/ScrollOverlay.svelte +121 -0
  48. package/src/components/grid/stores/bounds.js +16 -0
  49. package/src/components/grid/stores/clipboard.js +34 -0
  50. package/src/components/grid/stores/columns.js +206 -0
  51. package/src/components/grid/stores/index.js +48 -0
  52. package/src/components/grid/stores/menu.js +49 -0
  53. package/src/components/grid/stores/pagination.js +24 -0
  54. package/src/components/grid/stores/reorder.js +155 -0
  55. package/src/components/grid/stores/resize.js +124 -0
  56. package/src/components/grid/stores/rows.js +467 -0
  57. package/src/components/grid/stores/scroll.js +201 -0
  58. package/src/components/grid/stores/ui.js +159 -0
  59. package/src/components/grid/stores/users.js +115 -0
  60. package/src/components/grid/stores/validation.js +45 -0
  61. package/src/components/grid/stores/viewport.js +145 -0
  62. package/src/components/index.js +1 -0
  63. package/src/fetch/DataFetch.js +27 -21
  64. package/src/fetch/QueryFetch.js +1 -1
  65. package/src/themes/midnight.css +3 -0
  66. package/src/themes/nord.css +3 -0
  67. package/src/utils/utils.js +20 -0
@@ -0,0 +1,518 @@
1
+ <script context="module">
2
+ // We can create a module level cache for all relationship cells to avoid
3
+ // having to fetch the table definition one time for each cell
4
+ let primaryDisplayCache = {}
5
+
6
+ const getPrimaryDisplayForTableId = async (API, tableId) => {
7
+ if (primaryDisplayCache[tableId]) {
8
+ return primaryDisplayCache[tableId]
9
+ }
10
+ const definition = await API.fetchTableDefinition(tableId)
11
+ const primaryDisplay =
12
+ definition?.primaryDisplay || definition?.schema?.[0]?.name
13
+ primaryDisplayCache[tableId] = primaryDisplay
14
+ return primaryDisplay
15
+ }
16
+ </script>
17
+
18
+ <script>
19
+ import { getColor } from "../lib/utils"
20
+ import { onMount, getContext } from "svelte"
21
+ import { Icon, Input, ProgressCircle, clickOutside } from "@budibase/bbui"
22
+ import { debounce } from "../../../utils/utils"
23
+
24
+ export let value
25
+ export let api
26
+ export let readonly
27
+ export let focused
28
+ export let schema
29
+ export let onChange
30
+ export let invertX = false
31
+ export let invertY = false
32
+ export let contentLines = 1
33
+
34
+ const { API, dispatch } = getContext("grid")
35
+ const color = getColor(0)
36
+
37
+ let isOpen = false
38
+ let searchResults
39
+ let searchString
40
+ let lastSearchString
41
+ let primaryDisplay
42
+ let candidateIndex
43
+ let lastSearchId
44
+ let searching = false
45
+
46
+ $: oneRowOnly = schema?.relationshipType === "one-to-many"
47
+ $: editable = focused && !readonly
48
+ $: lookupMap = buildLookupMap(value, isOpen)
49
+ $: debouncedSearch(searchString)
50
+ $: {
51
+ if (!focused) {
52
+ close()
53
+ }
54
+ }
55
+
56
+ // Builds a lookup map to quickly check which rows are selected
57
+ const buildLookupMap = (value, isOpen) => {
58
+ let map = {}
59
+ if (!isOpen || !value?.length) {
60
+ return map
61
+ }
62
+ for (let i = 0; i < value.length; i++) {
63
+ map[value[i]._id] = true
64
+ }
65
+ return map
66
+ }
67
+
68
+ // Checks if a certain row is currently selected
69
+ const isRowSelected = row => {
70
+ if (!row?._id) {
71
+ return false
72
+ }
73
+ return lookupMap?.[row._id] === true
74
+ }
75
+
76
+ // Search for rows based on the search string
77
+ const search = async (searchString, force = false) => {
78
+ // Avoid update state at all if we've already handled the update and this is
79
+ // a wasted search due to svelte reactivity
80
+ if (!force && !searchString && !lastSearchString) {
81
+ return
82
+ }
83
+
84
+ // Reset state if this search is invalid
85
+ if (!schema?.tableId || !isOpen) {
86
+ lastSearchString = null
87
+ candidateIndex = null
88
+ searchResults = []
89
+ return
90
+ }
91
+
92
+ // Search for results, using IDs to track invocations and ensure we're
93
+ // handling the latest update
94
+ lastSearchId = Math.random()
95
+ searching = true
96
+ const thisSearchId = lastSearchId
97
+ const results = await API.searchTable({
98
+ paginate: false,
99
+ tableId: schema.tableId,
100
+ limit: 20,
101
+ query: {
102
+ string: {
103
+ [`1:${primaryDisplay}`]: searchString || "",
104
+ },
105
+ },
106
+ })
107
+ searching = false
108
+
109
+ // In case searching takes longer than our debounced update, abandon these
110
+ // results
111
+ if (thisSearchId !== lastSearchId) {
112
+ return
113
+ }
114
+
115
+ // Sort and process results
116
+ searchResults = sortRows(
117
+ results.rows?.map(row => ({
118
+ ...row,
119
+ primaryDisplay: row[primaryDisplay],
120
+ }))
121
+ )
122
+ candidateIndex = searchResults?.length ? 0 : null
123
+ lastSearchString = searchString
124
+ }
125
+
126
+ // Debounced version of searching
127
+ const debouncedSearch = debounce(search, 250)
128
+
129
+ // Alphabetically sorts rows by their primary display column
130
+ const sortRows = rows => {
131
+ if (!rows?.length) {
132
+ return []
133
+ }
134
+ return rows.slice().sort((a, b) => {
135
+ return a.primaryDisplay < b.primaryDisplay ? -1 : 1
136
+ })
137
+ }
138
+
139
+ const open = async () => {
140
+ isOpen = true
141
+
142
+ // Find the primary display for the related table
143
+ if (!primaryDisplay) {
144
+ searching = true
145
+ primaryDisplay = await getPrimaryDisplayForTableId(API, schema.tableId)
146
+ }
147
+
148
+ // Show initial list of results
149
+ await search(null, true)
150
+ }
151
+
152
+ const close = () => {
153
+ isOpen = false
154
+ searchResults = []
155
+ searchString = null
156
+ lastSearchString = null
157
+ candidateIndex = null
158
+ }
159
+
160
+ const onKeyDown = e => {
161
+ if (!isOpen) {
162
+ return false
163
+ }
164
+ if (e.key === "ArrowDown") {
165
+ // Select next result on down arrow
166
+ e.preventDefault()
167
+ if (candidateIndex == null) {
168
+ candidateIndex = 0
169
+ } else {
170
+ candidateIndex = Math.min(searchResults.length - 1, candidateIndex + 1)
171
+ }
172
+ } else if (e.key === "ArrowUp") {
173
+ // Select previous result on up array
174
+ e.preventDefault()
175
+ if (candidateIndex === 0) {
176
+ candidateIndex = null
177
+ } else if (candidateIndex != null) {
178
+ candidateIndex = Math.max(0, candidateIndex - 1)
179
+ }
180
+ } else if (e.key === "Enter") {
181
+ // Toggle the highlighted result on enter press
182
+ if (candidateIndex != null && searchResults[candidateIndex] != null) {
183
+ toggleRow(searchResults[candidateIndex])
184
+ }
185
+ }
186
+ return true
187
+ }
188
+
189
+ // Toggles whether a row is included in the relationship or not
190
+ const toggleRow = async row => {
191
+ if (value?.some(x => x._id === row._id)) {
192
+ // If the row is already included, remove it and update the candidate
193
+ // row to be the the same position if possible
194
+ if (oneRowOnly) {
195
+ await onChange([])
196
+ } else {
197
+ const newValue = value.filter(x => x._id !== row._id)
198
+ if (!newValue.length) {
199
+ candidateIndex = null
200
+ } else {
201
+ candidateIndex = Math.min(candidateIndex, newValue.length - 1)
202
+ }
203
+ await onChange(newValue)
204
+ }
205
+ } else {
206
+ // If we don't have this row, include it
207
+ if (oneRowOnly) {
208
+ await onChange([row])
209
+ } else {
210
+ await onChange(sortRows([...(value || []), row]))
211
+ }
212
+ candidateIndex = null
213
+ }
214
+ close()
215
+ }
216
+
217
+ const showRelationship = async id => {
218
+ const relatedRow = await API.fetchRow({
219
+ tableId: schema.tableId,
220
+ rowId: id,
221
+ })
222
+ dispatch("edit-row", relatedRow)
223
+ }
224
+
225
+ const readable = value => {
226
+ if (value == null) {
227
+ return ""
228
+ }
229
+ if (value instanceof Object) {
230
+ return JSON.stringify(value)
231
+ }
232
+ return value
233
+ }
234
+
235
+ onMount(() => {
236
+ api = {
237
+ focus: open,
238
+ blur: close,
239
+ isActive: () => isOpen,
240
+ onKeyDown,
241
+ }
242
+ })
243
+ </script>
244
+
245
+ <div class="wrapper" class:editable class:focused style="--color:{color};">
246
+ <div class="container">
247
+ <div
248
+ class="values"
249
+ class:wrap={editable || contentLines > 1}
250
+ on:wheel={e => (focused ? e.stopPropagation() : null)}
251
+ >
252
+ {#each value || [] as relationship, idx}
253
+ {#if relationship.primaryDisplay}
254
+ <div class="badge">
255
+ <span
256
+ on:click={editable
257
+ ? () => showRelationship(relationship._id)
258
+ : null}
259
+ >
260
+ {readable(relationship.primaryDisplay)}
261
+ </span>
262
+ {#if editable}
263
+ <Icon
264
+ name="Close"
265
+ size="XS"
266
+ hoverable
267
+ on:click={() => toggleRow(relationship)}
268
+ />
269
+ {/if}
270
+ </div>
271
+ {/if}
272
+ {/each}
273
+ {#if editable}
274
+ <div class="add" on:click={open}>
275
+ <Icon name="Add" size="S" />
276
+ </div>
277
+ {/if}
278
+ </div>
279
+ {#if value?.length}
280
+ <div class="count">
281
+ {value?.length || 0}
282
+ </div>
283
+ {/if}
284
+ </div>
285
+
286
+ {#if isOpen}
287
+ <div
288
+ class="dropdown"
289
+ class:invertX
290
+ class:invertY
291
+ on:wheel|stopPropagation
292
+ use:clickOutside={close}
293
+ >
294
+ <div class="search">
295
+ <Input
296
+ autofocus
297
+ quiet
298
+ type="text"
299
+ bind:value={searchString}
300
+ placeholder={primaryDisplay ? `Search by ${primaryDisplay}` : null}
301
+ />
302
+ </div>
303
+ {#if searching}
304
+ <div class="searching">
305
+ <ProgressCircle size="S" />
306
+ </div>
307
+ {:else if searchResults?.length}
308
+ <div class="results">
309
+ {#each searchResults as row, idx}
310
+ <div
311
+ class="result"
312
+ on:click={() => toggleRow(row)}
313
+ class:candidate={idx === candidateIndex}
314
+ on:mouseenter={() => (candidateIndex = idx)}
315
+ >
316
+ <div class="badge">
317
+ <span>
318
+ {readable(row.primaryDisplay)}
319
+ </span>
320
+ </div>
321
+ {#if isRowSelected(row)}
322
+ <Icon
323
+ size="S"
324
+ name="Checkmark"
325
+ color="var(--spectrum-global-color-blue-400)"
326
+ />
327
+ {/if}
328
+ </div>
329
+ {/each}
330
+ </div>
331
+ {/if}
332
+ </div>
333
+ {/if}
334
+ </div>
335
+
336
+ <style>
337
+ .wrapper {
338
+ flex: 1 1 auto;
339
+ align-self: flex-start;
340
+ min-height: var(--row-height);
341
+ max-height: var(--row-height);
342
+ overflow: hidden;
343
+ --max-relationship-height: 120px;
344
+ }
345
+ .wrapper.focused {
346
+ position: absolute;
347
+ top: 0;
348
+ left: 0;
349
+ width: 100%;
350
+ background: var(--cell-background);
351
+ z-index: 1;
352
+ max-height: none;
353
+ overflow: visible;
354
+ }
355
+
356
+ .container {
357
+ min-height: var(--row-height);
358
+ overflow: hidden;
359
+ display: flex;
360
+ align-items: flex-start;
361
+ }
362
+ .focused .container {
363
+ overflow-y: auto;
364
+ border-radius: 2px;
365
+ max-height: var(--max-relationship-height);
366
+ }
367
+ .focused .container:after {
368
+ content: " ";
369
+ position: absolute;
370
+ top: 0;
371
+ left: 0;
372
+ height: 100%;
373
+ width: 100%;
374
+ border: 2px solid var(--cell-color);
375
+ pointer-events: none;
376
+ border-radius: 2px;
377
+ box-sizing: border-box;
378
+ }
379
+
380
+ .values {
381
+ display: flex;
382
+ flex-direction: row;
383
+ justify-content: flex-start;
384
+ align-items: flex-start;
385
+ flex: 1 1 auto;
386
+ grid-column-gap: var(--cell-spacing);
387
+ grid-row-gap: var(--cell-padding);
388
+ overflow: hidden;
389
+ padding: var(--cell-padding);
390
+ flex-wrap: nowrap;
391
+ }
392
+ .values.wrap {
393
+ flex-wrap: wrap;
394
+ }
395
+ .count {
396
+ display: none;
397
+ position: absolute;
398
+ top: 0;
399
+ right: 0;
400
+ color: var(--spectrum-global-color-gray-500);
401
+ padding: var(--cell-padding) var(--cell-padding) var(--cell-padding) 20px;
402
+ background: linear-gradient(
403
+ to right,
404
+ transparent 0%,
405
+ var(--cell-background) 40%
406
+ );
407
+ }
408
+ .wrapper:hover:not(.focused) .count {
409
+ display: block;
410
+ }
411
+ .badge {
412
+ flex: 0 0 auto;
413
+ padding: 0 var(--cell-padding);
414
+ background: var(--color);
415
+ border-radius: var(--cell-padding);
416
+ user-select: none;
417
+ display: flex;
418
+ align-items: center;
419
+ gap: var(--cell-spacing);
420
+ height: 20px;
421
+ max-width: 100%;
422
+ }
423
+ .badge span {
424
+ overflow: hidden;
425
+ white-space: nowrap;
426
+ text-overflow: ellipsis;
427
+ }
428
+ .editable .values .badge span:hover {
429
+ cursor: pointer;
430
+ text-decoration: underline;
431
+ }
432
+
433
+ .add {
434
+ background: var(--spectrum-global-color-gray-200);
435
+ padding: 4px;
436
+ border-radius: 4px;
437
+ }
438
+ .add :global(.spectrum-Icon) {
439
+ width: 12px;
440
+ height: 12px;
441
+ }
442
+ .add:hover {
443
+ background: var(--spectrum-global-color-gray-300);
444
+ cursor: pointer;
445
+ }
446
+
447
+ .dropdown {
448
+ position: absolute;
449
+ top: 100%;
450
+ left: 0;
451
+ width: 100%;
452
+ max-height: calc(
453
+ var(--max-cell-render-height) + var(--row-height) -
454
+ var(--max-relationship-height)
455
+ );
456
+ background: var(--background);
457
+ border: var(--cell-border);
458
+ box-shadow: 0 0 20px -4px rgba(0, 0, 0, 0.15);
459
+ display: flex;
460
+ flex-direction: column;
461
+ align-items: stretch;
462
+ padding: 0 0 8px 0;
463
+ }
464
+ .dropdown.invertY {
465
+ transform: translateY(-100%);
466
+ top: -1px;
467
+ }
468
+ .dropdown.invertX {
469
+ left: auto;
470
+ right: 0;
471
+ }
472
+
473
+ .searching {
474
+ padding: var(--cell-padding);
475
+ display: flex;
476
+ justify-content: center;
477
+ }
478
+ .results {
479
+ overflow-y: auto;
480
+ overflow-x: hidden;
481
+ display: flex;
482
+ flex-direction: column;
483
+ align-items: stretch;
484
+ }
485
+ .result {
486
+ padding: 0 var(--cell-padding);
487
+ flex: 0 0 var(--default-row-height);
488
+ display: flex;
489
+ gap: var(--cell-spacing);
490
+ justify-content: space-between;
491
+ align-items: center;
492
+ }
493
+ .result.candidate {
494
+ background-color: var(--spectrum-global-color-gray-200);
495
+ cursor: pointer;
496
+ }
497
+ .result .badge {
498
+ max-width: calc(100% - 30px);
499
+ }
500
+
501
+ .search {
502
+ flex: 0 0 calc(var(--default-row-height) - 1px);
503
+ display: flex;
504
+ align-items: center;
505
+ margin: 4px var(--cell-padding);
506
+ width: calc(100% - 2 * var(--cell-padding));
507
+ }
508
+ .search :global(.spectrum-Textfield) {
509
+ min-width: 0;
510
+ width: 100%;
511
+ }
512
+ .search :global(.spectrum-Textfield-input) {
513
+ font-size: 13px;
514
+ }
515
+ .search :global(.spectrum-Form-item) {
516
+ flex: 1 1 auto;
517
+ }
518
+ </style>
@@ -0,0 +1,111 @@
1
+ <script>
2
+ import { onMount } from "svelte"
3
+
4
+ export let value
5
+ export let focused = false
6
+ export let onChange
7
+ export let type = "text"
8
+ export let readonly = false
9
+ export let api
10
+
11
+ let input
12
+ let active = false
13
+
14
+ $: editable = focused && !readonly
15
+
16
+ const handleChange = e => {
17
+ onChange(e.target.value)
18
+ }
19
+
20
+ const onKeyDown = e => {
21
+ if (!active) {
22
+ return false
23
+ }
24
+ if (e.key === "Enter") {
25
+ input?.blur()
26
+ const event = new KeyboardEvent("keydown", { key: "ArrowDown" })
27
+ document.dispatchEvent(event)
28
+ }
29
+ return true
30
+ }
31
+
32
+ onMount(() => {
33
+ api = {
34
+ focus: () => input?.focus(),
35
+ blur: () => input?.blur(),
36
+ isActive: () => active,
37
+ onKeyDown,
38
+ }
39
+ })
40
+ </script>
41
+
42
+ {#if editable}
43
+ <input
44
+ bind:this={input}
45
+ on:focus={() => (active = true)}
46
+ on:blur={() => (active = false)}
47
+ {type}
48
+ value={value || ""}
49
+ on:change={handleChange}
50
+ spellcheck="false"
51
+ />
52
+ {:else}
53
+ <div class="text-cell" class:number={type === "number"}>
54
+ <div class="value">
55
+ {value || ""}
56
+ </div>
57
+ </div>
58
+ {/if}
59
+
60
+ <style>
61
+ .text-cell {
62
+ flex: 1 1 auto;
63
+ padding: var(--cell-padding);
64
+ align-self: stretch;
65
+ display: flex;
66
+ align-items: flex-start;
67
+ overflow: hidden;
68
+ }
69
+ .text-cell.number {
70
+ justify-content: flex-end;
71
+ }
72
+ .value {
73
+ display: -webkit-box;
74
+ -webkit-line-clamp: var(--content-lines);
75
+ -webkit-box-orient: vertical;
76
+ overflow: hidden;
77
+ line-height: 20px;
78
+ }
79
+ .number .value {
80
+ -webkit-line-clamp: 1;
81
+ }
82
+ input {
83
+ flex: 1 1 auto;
84
+ border: none;
85
+ padding: var(--cell-padding);
86
+ overflow: hidden;
87
+ text-overflow: ellipsis;
88
+ white-space: nowrap;
89
+ background: none;
90
+ font-size: var(--cell-font-size);
91
+ font-family: var(--font-sans);
92
+ color: inherit;
93
+ line-height: 20px;
94
+ }
95
+ input:focus {
96
+ outline: none;
97
+ }
98
+ input[type="number"] {
99
+ text-align: right;
100
+ }
101
+
102
+ /* Hide arrows for number fields */
103
+ input::-webkit-outer-spin-button,
104
+ input::-webkit-inner-spin-button {
105
+ -webkit-appearance: none;
106
+ margin: 0;
107
+ }
108
+ input[type="number"] {
109
+ -moz-appearance: textfield;
110
+ }
111
+ </style>
@@ -0,0 +1,16 @@
1
+ <script>
2
+ import { ActionButton } from "@budibase/bbui"
3
+ import { getContext } from "svelte"
4
+
5
+ const { config, dispatch } = getContext("grid")
6
+ </script>
7
+
8
+ <ActionButton
9
+ icon="TableColumnAddRight"
10
+ quiet
11
+ size="M"
12
+ on:click={() => dispatch("add-column")}
13
+ disabled={!$config.allowAddColumns}
14
+ >
15
+ Add column
16
+ </ActionButton>
@@ -0,0 +1,18 @@
1
+ <script>
2
+ import { ActionButton } from "@budibase/bbui"
3
+ import { getContext } from "svelte"
4
+
5
+ const { dispatch, columns, stickyColumn, config, loaded } = getContext("grid")
6
+ </script>
7
+
8
+ <ActionButton
9
+ icon="TableRowAddBottom"
10
+ quiet
11
+ size="M"
12
+ on:click={() => dispatch("add-row-inline")}
13
+ disabled={!loaded ||
14
+ !$config.allowAddRows ||
15
+ (!$columns.length && !$stickyColumn)}
16
+ >
17
+ Add row
18
+ </ActionButton>
@@ -0,0 +1,46 @@
1
+ <script>
2
+ import { Button } from "@budibase/bbui"
3
+ </script>
4
+
5
+ <div class="beta-background" />
6
+ <div class="beta">
7
+ Enjoying the Grid?
8
+ <Button
9
+ size="M"
10
+ cta
11
+ on:click={() => window.open("https://t.maze.co/156382627", "_blank")}
12
+ >
13
+ Give Feedback
14
+ </Button>
15
+ </div>
16
+
17
+ <style>
18
+ .beta {
19
+ position: absolute;
20
+ bottom: 32px;
21
+ right: 32px;
22
+ display: flex;
23
+ flex-direction: row;
24
+ align-items: center;
25
+ gap: 16px;
26
+ }
27
+ .beta :global(.spectrum-Button) {
28
+ background: var(--spectrum-global-color-magenta-400);
29
+ border-color: var(--spectrum-global-color-magenta-400);
30
+ }
31
+ .beta-background {
32
+ z-index: 0;
33
+ pointer-events: none;
34
+ position: absolute;
35
+ bottom: -230px;
36
+ right: -105px;
37
+ width: 1400px;
38
+ height: 320px;
39
+ transform: rotate(-22deg);
40
+ background: linear-gradient(
41
+ to top,
42
+ var(--cell-background) 20%,
43
+ transparent
44
+ );
45
+ }
46
+ </style>