@budibase/bbui 1.0.0 → 1.0.1

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 (128) hide show
  1. package/LICENSE +375 -0
  2. package/README.md +19 -17
  3. package/dist/bbui.es.js +2 -0
  4. package/dist/bbui.es.js.map +1 -0
  5. package/package.json +68 -26
  6. package/src/ActionButton/ActionButton.svelte +83 -0
  7. package/src/ActionGroup/ActionGroup.svelte +26 -0
  8. package/src/ActionMenu/ActionMenu.svelte +43 -0
  9. package/src/Actions/ClickOutside.svench.svx +30 -0
  10. package/src/Actions/PositionDropdown.svench.svx +81 -0
  11. package/src/Actions/autoresize_textarea.js +14 -0
  12. package/src/Actions/click_outside.js +18 -0
  13. package/src/Actions/position_dropdown.js +66 -0
  14. package/src/Avatar/Avatar.svelte +49 -0
  15. package/src/Badge/Badge.svelte +29 -0
  16. package/src/Button/Button.svelte +52 -0
  17. package/src/Button/Button.svench +138 -0
  18. package/src/ButtonGroup/ButtonGroup.svelte +19 -0
  19. package/src/ClearButton/ClearButton.svelte +19 -0
  20. package/src/ColorPicker/ColorPicker.svelte +292 -0
  21. package/src/DetailSummary/DetailSummary.svelte +83 -0
  22. package/src/Divider/Divider.svelte +25 -0
  23. package/src/Drawer/Drawer.svelte +85 -0
  24. package/src/Drawer/Drawer.svench +42 -0
  25. package/src/Drawer/DrawerContent.svelte +46 -0
  26. package/src/Form/Checkbox.svelte +23 -0
  27. package/src/Form/Combobox.svelte +44 -0
  28. package/src/Form/Core/Checkbox.svelte +67 -0
  29. package/src/Form/Core/Combobox.svelte +156 -0
  30. package/src/Form/Core/DatePicker.svelte +183 -0
  31. package/src/Form/Core/Dropzone.svelte +393 -0
  32. package/src/Form/Core/Multiselect.svelte +88 -0
  33. package/src/Form/Core/Picker.svelte +241 -0
  34. package/src/Form/Core/RadioGroup.svelte +47 -0
  35. package/src/Form/Core/Search.svelte +101 -0
  36. package/src/Form/Core/Select.svelte +81 -0
  37. package/src/Form/Core/Stepper.svelte +172 -0
  38. package/src/Form/Core/Switch.svelte +35 -0
  39. package/src/Form/Core/TextArea.svelte +60 -0
  40. package/src/Form/Core/TextField.svelte +104 -0
  41. package/src/Form/Core/index.js +12 -0
  42. package/src/Form/DatePicker.svelte +33 -0
  43. package/src/Form/Dropzone.svelte +34 -0
  44. package/src/Form/Field.svelte +41 -0
  45. package/src/Form/FieldLabel.svelte +27 -0
  46. package/src/Form/Input.svelte +38 -45
  47. package/src/Form/Multiselect.svelte +39 -0
  48. package/src/Form/RadioGroup.svelte +38 -0
  49. package/src/Form/Search.svelte +32 -0
  50. package/src/Form/Select.svelte +45 -43
  51. package/src/Form/Stepper.svelte +45 -0
  52. package/src/Form/TextArea.svelte +25 -44
  53. package/src/Form/Toggle.svelte +23 -0
  54. package/src/Icon/Icon.svelte +47 -0
  55. package/src/InlineAlert/InlineAlert.svelte +56 -0
  56. package/src/Label/Label.svelte +67 -0
  57. package/src/Layout/Layout.svelte +75 -0
  58. package/src/Layout/Page.svelte +25 -0
  59. package/src/Link/Link.svelte +22 -0
  60. package/src/List/Items/DetailSummary.svench +53 -0
  61. package/src/Menu/Item.svelte +45 -0
  62. package/src/Menu/Menu.svelte +7 -0
  63. package/src/Menu/Menu.svench +19 -0
  64. package/src/Menu/Section.svelte +10 -0
  65. package/src/Menu/Separator.svelte +1 -0
  66. package/src/Modal/Content.svelte +6 -0
  67. package/src/Modal/CustomContent.svelte +38 -0
  68. package/src/Modal/Modal.svelte +133 -0
  69. package/src/Modal/Modal.svench +117 -0
  70. package/src/Modal/ModalContent.svelte +139 -0
  71. package/src/Modal/QuizModal.svelte +54 -0
  72. package/src/Notification/Notification.svelte +20 -0
  73. package/src/Notification/NotificationDisplay.svelte +36 -0
  74. package/src/Pagination/Pagination.svelte +57 -0
  75. package/src/Popover/Popover.svelte +52 -0
  76. package/src/Popover/Popover.svench +121 -0
  77. package/src/ProgressBar/ProgressBar.svelte +54 -0
  78. package/src/ProgressCircle/ProgressCircle.svelte +62 -0
  79. package/src/SideNavigation/Item.svelte +45 -0
  80. package/src/SideNavigation/Navigation.svelte +12 -0
  81. package/src/StatusLight/StatusLight.svelte +41 -0
  82. package/src/Stores/Notifications.svench.svx +78 -0
  83. package/src/Stores/notifications.js +56 -0
  84. package/src/Table/ArrayRenderer.svelte +17 -0
  85. package/src/Table/AttachmentRenderer.svelte +55 -0
  86. package/src/Table/BooleanRenderer.svelte +42 -0
  87. package/src/Table/CellRenderer.svelte +37 -0
  88. package/src/Table/DateTimeRenderer.svelte +13 -0
  89. package/src/Table/InternalRenderer.svelte +28 -0
  90. package/src/Table/RelationshipRenderer.svelte +40 -0
  91. package/src/Table/SelectEditRenderer.svelte +16 -0
  92. package/src/Table/StringRenderer.svelte +13 -0
  93. package/src/Table/Table.svelte +506 -0
  94. package/src/Table/index.js +1 -0
  95. package/src/Tabs/Tab.svelte +66 -0
  96. package/src/Tabs/Tabs.svelte +105 -0
  97. package/src/Tags/Tag.svelte +43 -0
  98. package/src/Tags/Tags.svelte +7 -0
  99. package/src/Tooltip/Tooltip.svelte +24 -0
  100. package/src/TreeView/Item.svelte +41 -0
  101. package/src/TreeView/Tree.svelte +16 -0
  102. package/src/Typography/Body.svelte +19 -0
  103. package/src/Typography/Code.svelte +10 -0
  104. package/src/Typography/Detail.svelte +15 -0
  105. package/src/Typography/Heading.svelte +16 -0
  106. package/src/bbui.css +96 -0
  107. package/src/context.js +3 -0
  108. package/src/index.js +75 -10
  109. package/src/spectrum-icons-rollup.js +31 -0
  110. package/src/spectrum-icons-vite.js +31 -0
  111. package/src/utils/helpers.js +8 -0
  112. package/dist/bundle.css +0 -6
  113. package/dist/bundle.css.map +0 -18
  114. package/dist/index.js +0 -919
  115. package/dist/index.mjs +0 -906
  116. package/src/Branding/Budibase-logo.svelte +0 -68
  117. package/src/Branding/Logo.svelte +0 -16
  118. package/src/Branding/Logo.svench +0 -8
  119. package/src/Form/Button.svelte +0 -52
  120. package/src/Form/Button.svench +0 -26
  121. package/src/Form/Input.svench +0 -8
  122. package/src/Form/Select.svench +0 -8
  123. package/src/Form/TextArea.svench +0 -8
  124. package/src/Links/Home.svelte +0 -49
  125. package/src/Links/Home.svench +0 -19
  126. package/src/actions/autoresize_textarea.js +0 -14
  127. package/src/icons/Contribution.svelte +0 -14
  128. package/src/index.svench.svx +0 -35
@@ -0,0 +1,393 @@
1
+ <script>
2
+ import "@spectrum-css/dropzone/dist/index-vars.css"
3
+ import "@spectrum-css/typography/dist/index-vars.css"
4
+ import "@spectrum-css/illustratedmessage/dist/index-vars.css"
5
+ import { createEventDispatcher } from "svelte"
6
+ import { generateID } from "../../utils/helpers"
7
+ import Icon from "../../Icon/Icon.svelte"
8
+ import Link from "../../Link/Link.svelte"
9
+
10
+ const BYTES_IN_KB = 1000
11
+ const BYTES_IN_MB = 1000000
12
+
13
+ export let value = []
14
+ export let id = null
15
+ export let disabled = false
16
+ export let fileSizeLimit = BYTES_IN_MB * 20
17
+ export let processFiles = null
18
+ export let handleFileTooLarge = null
19
+ export let gallery = true
20
+ export let error = null
21
+
22
+ const dispatch = createEventDispatcher()
23
+ const imageExtensions = [
24
+ "png",
25
+ "tiff",
26
+ "gif",
27
+ "raw",
28
+ "jpg",
29
+ "jpeg",
30
+ "svg",
31
+ "bmp",
32
+ "jfif",
33
+ ]
34
+
35
+ const fieldId = id || generateID()
36
+ let selectedImageIdx = 0
37
+ let fileDragged = false
38
+ let selectedUrl
39
+ $: selectedImage = value?.[selectedImageIdx] ?? null
40
+ $: fileCount = value?.length ?? 0
41
+ $: isImage =
42
+ imageExtensions.includes(selectedImage?.extension?.toLowerCase()) ||
43
+ selectedImage?.type?.startsWith("image")
44
+
45
+ $: {
46
+ if (selectedImage?.url) {
47
+ selectedUrl = selectedImage?.url
48
+ } else if (selectedImage) {
49
+ try {
50
+ let reader = new FileReader()
51
+ reader.readAsDataURL(selectedImage)
52
+ reader.onload = e => {
53
+ selectedUrl = e.target.result
54
+ }
55
+ } catch (error) {
56
+ selectedUrl = null
57
+ }
58
+ }
59
+ }
60
+
61
+ async function processFileList(fileList) {
62
+ if (
63
+ handleFileTooLarge &&
64
+ Array.from(fileList).some(file => file.size >= fileSizeLimit)
65
+ ) {
66
+ handleFileTooLarge(fileSizeLimit, value)
67
+ return
68
+ }
69
+ if (processFiles) {
70
+ const processedFiles = await processFiles(fileList)
71
+ const newValue = [...value, ...processedFiles]
72
+ dispatch("change", newValue)
73
+ selectedImageIdx = newValue.length - 1
74
+ } else {
75
+ dispatch("change", fileList)
76
+ }
77
+ }
78
+
79
+ async function removeFile() {
80
+ dispatch(
81
+ "change",
82
+ value.filter((x, idx) => idx !== selectedImageIdx)
83
+ )
84
+ selectedImageIdx = 0
85
+ }
86
+
87
+ function navigateLeft() {
88
+ selectedImageIdx -= 1
89
+ }
90
+
91
+ function navigateRight() {
92
+ selectedImageIdx += 1
93
+ }
94
+
95
+ function handleFile(evt) {
96
+ processFileList(evt.target.files)
97
+ }
98
+
99
+ function handleDragOver(evt) {
100
+ evt.preventDefault()
101
+ fileDragged = true
102
+ }
103
+
104
+ function handleDragLeave(evt) {
105
+ evt.preventDefault()
106
+ fileDragged = false
107
+ }
108
+
109
+ function handleDrop(evt) {
110
+ evt.preventDefault()
111
+ processFileList(evt.dataTransfer.files)
112
+ fileDragged = false
113
+ }
114
+ </script>
115
+
116
+ <div class="container">
117
+ {#if selectedImage}
118
+ {#if gallery}
119
+ <div class="gallery">
120
+ <div class="title">
121
+ <div class="filename">
122
+ {#if selectedUrl}
123
+ <Link href={selectedUrl}>{selectedImage.name}</Link>
124
+ {:else}
125
+ {selectedImage.name}
126
+ {/if}
127
+ </div>
128
+ {#if selectedImage.size}
129
+ <div class="filesize">
130
+ {#if selectedImage.size <= BYTES_IN_MB}
131
+ {`${selectedImage.size / BYTES_IN_KB} KB`}
132
+ {:else}{`${selectedImage.size / BYTES_IN_MB} MB`}{/if}
133
+ </div>
134
+ {/if}
135
+ {#if !disabled}
136
+ <div class="delete-button" on:click={removeFile}>
137
+ <Icon name="Close" />
138
+ </div>
139
+ {/if}
140
+ </div>
141
+ {#if isImage}
142
+ <img alt="preview" src={selectedUrl} />
143
+ {:else}
144
+ <div class="placeholder">
145
+ <div class="extension">{selectedImage.extension}</div>
146
+ <div>Preview not supported</div>
147
+ </div>
148
+ {/if}
149
+ <div
150
+ class="nav left"
151
+ class:visible={selectedImageIdx > 0}
152
+ on:click={navigateLeft}
153
+ >
154
+ <Icon name="ChevronLeft" />
155
+ </div>
156
+ <div
157
+ class="nav right"
158
+ class:visible={selectedImageIdx < fileCount - 1}
159
+ on:click={navigateRight}
160
+ >
161
+ <Icon name="ChevronRight" />
162
+ </div>
163
+ <div class="footer">File {selectedImageIdx + 1} of {fileCount}</div>
164
+ </div>
165
+ {:else if value?.length}
166
+ {#each value as file}
167
+ <div class="gallery">
168
+ <div class="title">
169
+ <div class="filename">{file.name}</div>
170
+ {#if file.size}
171
+ <div class="filesize">
172
+ {#if file.size <= BYTES_IN_MB}
173
+ {`${file.size / BYTES_IN_KB} KB`}
174
+ {:else}{`${file.size / BYTES_IN_MB} MB`}{/if}
175
+ </div>
176
+ {/if}
177
+ {#if !disabled}
178
+ <div class="delete-button" on:click={removeFile}>
179
+ <Icon name="Close" />
180
+ </div>
181
+ {/if}
182
+ </div>
183
+ </div>
184
+ {/each}
185
+ {/if}
186
+ {/if}
187
+ <div
188
+ class="spectrum-Dropzone"
189
+ class:is-invalid={!!error}
190
+ class:disabled
191
+ role="region"
192
+ tabindex="0"
193
+ on:dragover={handleDragOver}
194
+ on:dragleave={handleDragLeave}
195
+ on:dragenter={handleDragOver}
196
+ on:drop={handleDrop}
197
+ class:is-dragged={fileDragged}
198
+ >
199
+ <div class="spectrum-IllustratedMessage spectrum-IllustratedMessage--cta">
200
+ <input
201
+ id={fieldId}
202
+ {disabled}
203
+ type="file"
204
+ multiple
205
+ on:change={handleFile}
206
+ />
207
+ <svg
208
+ class="spectrum-IllustratedMessage-illustration"
209
+ width="125"
210
+ height="60"
211
+ viewBox="0 0 199 97.7"
212
+ >
213
+ <defs>
214
+ <style>
215
+ .cls-1,
216
+ .cls-2 {
217
+ fill: none;
218
+ stroke-linecap: round;
219
+ stroke-linejoin: round;
220
+ }
221
+ .cls-1 {
222
+ stroke-width: 3px;
223
+ }
224
+ .cls-2 {
225
+ stroke-width: 2px;
226
+ }
227
+ </style>
228
+ </defs>
229
+ <path
230
+ class="cls-1"
231
+ d="M110.53,85.66,100.26,95.89a1.09,1.09,0,0,1-1.52,0L88.47,85.66"
232
+ />
233
+ <line class="cls-1" x1="99.5" y1="95.5" x2="99.5" y2="58.5" />
234
+ <path class="cls-1" d="M105.5,73.5h19a2,2,0,0,0,2-2v-43" />
235
+ <path
236
+ class="cls-1"
237
+ d="M126.5,22.5h-19a2,2,0,0,1-2-2V1.5h-31a2,2,0,0,0-2,2v68a2,2,0,0,0,2,2h19"
238
+ />
239
+ <line class="cls-1" x1="105.5" y1="1.5" x2="126.5" y2="22.5" />
240
+ <path
241
+ class="cls-2"
242
+ d="M47.93,50.49a5,5,0,1,0-4.83-5A4.93,4.93,0,0,0,47.93,50.49Z"
243
+ />
244
+ <path
245
+ class="cls-2"
246
+ d="M36.6,65.93,42.05,60A2.06,2.06,0,0,1,45,60l12.68,13.2"
247
+ />
248
+ <path
249
+ class="cls-2"
250
+ d="M3.14,73.23,22.42,53.76a1.65,1.65,0,0,1,2.38,0l19.05,19.7"
251
+ />
252
+ <path
253
+ class="cls-1"
254
+ d="M139.5,36.5H196A1.49,1.49,0,0,1,197.5,38V72A1.49,1.49,0,0,1,196,73.5H141A1.49,1.49,0,0,1,139.5,72V32A1.49,1.49,0,0,1,141,30.5H154a2.43,2.43,0,0,1,1.67.66l6,5.66"
255
+ />
256
+ <rect
257
+ class="cls-1"
258
+ x="1.5"
259
+ y="34.5"
260
+ width="58"
261
+ height="39"
262
+ rx="2"
263
+ ry="2"
264
+ />
265
+ </svg>
266
+ <h2
267
+ class="spectrum-Heading spectrum-Heading--sizeL spectrum-Heading--light spectrum-IllustratedMessage-heading"
268
+ >
269
+ Drag and drop your file
270
+ </h2>
271
+ {#if !disabled}
272
+ <p
273
+ class="spectrum-Body spectrum-Body--sizeS spectrum-IllustratedMessage-description"
274
+ >
275
+ <label for={fieldId} class="spectrum-Link">
276
+ Select a file to upload
277
+ </label>
278
+ <br />
279
+ from your computer
280
+ </p>
281
+ {/if}
282
+ </div>
283
+ </div>
284
+ </div>
285
+
286
+ <style>
287
+ .container {
288
+ --spectrum-dropzone-padding: var(--spectrum-global-dimension-size-400);
289
+ --spectrum-heading-l-text-size: var(
290
+ --spectrum-global-dimension-font-size-400
291
+ );
292
+ }
293
+
294
+ .gallery,
295
+ .spectrum-Dropzone {
296
+ user-select: none;
297
+ }
298
+ .spectrum-Dropzone.is-invalid {
299
+ border-color: var(--spectrum-global-color-red-400);
300
+ }
301
+ input[type="file"] {
302
+ display: none;
303
+ }
304
+
305
+ .gallery {
306
+ display: flex;
307
+ flex-direction: column;
308
+ justify-content: flex-start;
309
+ align-items: stretch;
310
+ background-color: var(--spectrum-global-color-gray-50);
311
+ color: var(--spectrum-alias-text-color);
312
+ font-size: var(--spectrum-alias-item-text-size-m);
313
+ box-sizing: border-box;
314
+ border: var(--spectrum-alias-border-size-thin)
315
+ var(--spectrum-alias-border-color) solid;
316
+ border-radius: var(--spectrum-alias-border-radius-regular);
317
+ padding: 10px;
318
+ margin-bottom: 10px;
319
+ position: relative;
320
+ }
321
+ .placeholder,
322
+ img {
323
+ height: 120px;
324
+ max-width: 100%;
325
+ object-fit: contain;
326
+ margin: 20px 30px;
327
+ }
328
+ .title {
329
+ display: flex;
330
+ flex-direction: row;
331
+ justify-content: flex-start;
332
+ align-items: center;
333
+ }
334
+ .filename {
335
+ flex: 1 1 auto;
336
+ overflow: hidden;
337
+ text-overflow: ellipsis;
338
+ white-space: nowrap;
339
+ width: 0;
340
+ margin-right: 10px;
341
+ }
342
+ .placeholder {
343
+ display: flex;
344
+ flex-direction: column;
345
+ justify-content: center;
346
+ align-items: center;
347
+ }
348
+ .extension {
349
+ color: var(--spectrum-global-color-gray-600);
350
+ text-transform: uppercase;
351
+ font-weight: 600;
352
+ margin-bottom: 5px;
353
+ }
354
+
355
+ .nav {
356
+ padding: var(--spacing-xs);
357
+ position: absolute;
358
+ top: 50%;
359
+ border-radius: 5px;
360
+ display: none;
361
+ transition: all 0.3s;
362
+ }
363
+ .nav.visible {
364
+ display: block;
365
+ }
366
+ .nav:hover {
367
+ cursor: pointer;
368
+ color: var(--blue);
369
+ }
370
+ .left {
371
+ left: 5px;
372
+ }
373
+ .right {
374
+ right: 5px;
375
+ }
376
+ .delete-button {
377
+ transition: all 0.3s;
378
+ margin-left: 10px;
379
+ display: flex;
380
+ }
381
+ .delete-button:hover {
382
+ cursor: pointer;
383
+ color: var(--red);
384
+ }
385
+
386
+ .spectrum-Dropzone.disabled {
387
+ pointer-events: none;
388
+ background-color: var(--spectrum-global-color-gray-200);
389
+ }
390
+ .disabled .spectrum-Heading--sizeL {
391
+ color: var(--spectrum-alias-text-color-disabled);
392
+ }
393
+ </style>
@@ -0,0 +1,88 @@
1
+ <script>
2
+ import Picker from "./Picker.svelte"
3
+ import { createEventDispatcher } from "svelte"
4
+
5
+ export let value = []
6
+ export let id = null
7
+ export let placeholder = null
8
+ export let disabled = false
9
+ export let error = null
10
+ export let options = []
11
+ export let getOptionLabel = option => option
12
+ export let getOptionValue = option => option
13
+ export let readonly = false
14
+ export let autocomplete = false
15
+ export let sort = false
16
+
17
+ const dispatch = createEventDispatcher()
18
+ $: selectedLookupMap = getSelectedLookupMap(value)
19
+ $: optionLookupMap = getOptionLookupMap(options)
20
+ $: fieldText = getFieldText(value, optionLookupMap, placeholder)
21
+ $: isOptionSelected = optionValue => selectedLookupMap[optionValue] === true
22
+ $: toggleOption = makeToggleOption(selectedLookupMap, value)
23
+
24
+ const getFieldText = (value, map, placeholder) => {
25
+ if (value?.length) {
26
+ if (!map) {
27
+ return ""
28
+ }
29
+ const vals = value.map(option => map[option] || option).join(", ")
30
+ return `(${value.length}) ${vals}`
31
+ } else {
32
+ return placeholder || "Choose some options"
33
+ }
34
+ }
35
+
36
+ const getSelectedLookupMap = value => {
37
+ let map = {}
38
+ if (value?.length) {
39
+ value.forEach(option => {
40
+ if (option) {
41
+ map[option] = true
42
+ }
43
+ })
44
+ }
45
+ return map
46
+ }
47
+
48
+ const getOptionLookupMap = options => {
49
+ let map = null
50
+ if (options?.length) {
51
+ map = {}
52
+ options.forEach((option, idx) => {
53
+ const optionValue = getOptionValue(option, idx)
54
+ if (optionValue != null) {
55
+ map[optionValue] = getOptionLabel(option, idx) || ""
56
+ }
57
+ })
58
+ }
59
+ return map
60
+ }
61
+
62
+ const makeToggleOption = (map, value) => {
63
+ return optionValue => {
64
+ if (map[optionValue]) {
65
+ const filtered = value.filter(option => option !== optionValue)
66
+ dispatch("change", filtered)
67
+ } else {
68
+ dispatch("change", [...value, optionValue])
69
+ }
70
+ }
71
+ }
72
+ </script>
73
+
74
+ <Picker
75
+ {id}
76
+ {error}
77
+ {disabled}
78
+ {readonly}
79
+ {fieldText}
80
+ {options}
81
+ isPlaceholder={!value?.length}
82
+ {autocomplete}
83
+ {isOptionSelected}
84
+ {getOptionLabel}
85
+ {getOptionValue}
86
+ onSelectOption={toggleOption}
87
+ {sort}
88
+ />
@@ -0,0 +1,241 @@
1
+ <script>
2
+ import "@spectrum-css/picker/dist/index-vars.css"
3
+ import "@spectrum-css/popover/dist/index-vars.css"
4
+ import "@spectrum-css/menu/dist/index-vars.css"
5
+ import { fly } from "svelte/transition"
6
+ import { createEventDispatcher } from "svelte"
7
+ import clickOutside from "../../Actions/click_outside"
8
+ import Search from "./Search.svelte"
9
+
10
+ export let id = null
11
+ export let disabled = false
12
+ export let error = null
13
+ export let fieldText = ""
14
+ export let fieldIcon = ""
15
+ export let isPlaceholder = false
16
+ export let placeholderOption = null
17
+ export let options = []
18
+ export let isOptionSelected = () => false
19
+ export let onSelectOption = () => {}
20
+ export let getOptionLabel = option => option
21
+ export let getOptionValue = option => option
22
+ export let getOptionIcon = () => null
23
+ export let open = false
24
+ export let readonly = false
25
+ export let quiet = false
26
+ export let autoWidth = false
27
+ export let autocomplete = false
28
+ export let sort = false
29
+
30
+ const dispatch = createEventDispatcher()
31
+ let searchTerm = null
32
+
33
+ $: sortedOptions = getSortedOptions(options, getOptionLabel, sort)
34
+ $: filteredOptions = getFilteredOptions(
35
+ sortedOptions,
36
+ searchTerm,
37
+ getOptionLabel
38
+ )
39
+
40
+ const onClick = () => {
41
+ dispatch("click")
42
+ if (readonly) {
43
+ return
44
+ }
45
+ searchTerm = null
46
+ open = true
47
+ }
48
+
49
+ const getSortedOptions = (options, getLabel, sort) => {
50
+ if (!options?.length || !Array.isArray(options)) {
51
+ return []
52
+ }
53
+ if (!sort) {
54
+ return options
55
+ }
56
+ return options.sort((a, b) => {
57
+ const labelA = getLabel(a)
58
+ const labelB = getLabel(b)
59
+ return labelA > labelB ? 1 : -1
60
+ })
61
+ }
62
+
63
+ const getFilteredOptions = (options, term, getLabel) => {
64
+ if (autocomplete && term) {
65
+ const lowerCaseTerm = term.toLowerCase()
66
+ return options.filter(option =>
67
+ getLabel(option)?.toLowerCase().includes(lowerCaseTerm)
68
+ )
69
+ }
70
+ return options
71
+ }
72
+ </script>
73
+
74
+ <button
75
+ {id}
76
+ class="spectrum-Picker spectrum-Picker--sizeM"
77
+ class:spectrum-Picker--quiet={quiet}
78
+ {disabled}
79
+ class:is-invalid={!!error}
80
+ class:is-open={open}
81
+ aria-haspopup="listbox"
82
+ on:mousedown={onClick}
83
+ >
84
+ {#if fieldIcon}
85
+ <span class="icon-Placeholder-Padding">
86
+ <img src={fieldIcon} alt="icon" width="20" height="15" />
87
+ </span>
88
+ {/if}
89
+
90
+ <span
91
+ class="spectrum-Picker-label"
92
+ class:is-placeholder={isPlaceholder}
93
+ class:auto-width={autoWidth}
94
+ >
95
+ {fieldText}
96
+ </span>
97
+ {#if error}
98
+ <svg
99
+ class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
100
+ focusable="false"
101
+ aria-hidden="true"
102
+ aria-label="Folder"
103
+ >
104
+ <use xlink:href="#spectrum-icon-18-Alert" />
105
+ </svg>
106
+ {/if}
107
+ <svg
108
+ class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
109
+ focusable="false"
110
+ aria-hidden="true"
111
+ >
112
+ <use xlink:href="#spectrum-css-icon-Chevron100" />
113
+ </svg>
114
+ </button>
115
+ {#if open}
116
+ <div
117
+ use:clickOutside={() => (open = false)}
118
+ transition:fly|local={{ y: -20, duration: 200 }}
119
+ class="spectrum-Popover spectrum-Popover--bottom spectrum-Picker-popover is-open"
120
+ class:auto-width={autoWidth}
121
+ >
122
+ {#if autocomplete}
123
+ <Search
124
+ value={searchTerm}
125
+ on:change={event => (searchTerm = event.detail)}
126
+ {disabled}
127
+ placeholder="Search"
128
+ />
129
+ {/if}
130
+ <ul class="spectrum-Menu" role="listbox">
131
+ {#if placeholderOption}
132
+ <li
133
+ class="spectrum-Menu-item placeholder"
134
+ class:is-selected={isPlaceholder}
135
+ role="option"
136
+ aria-selected="true"
137
+ tabindex="0"
138
+ on:click={() => onSelectOption(null)}
139
+ >
140
+ <span class="spectrum-Menu-itemLabel">{placeholderOption}</span>
141
+ <svg
142
+ class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
143
+ focusable="false"
144
+ aria-hidden="true"
145
+ >
146
+ <use xlink:href="#spectrum-css-icon-Checkmark100" />
147
+ </svg>
148
+ </li>
149
+ {/if}
150
+ {#if filteredOptions.length}
151
+ {#each filteredOptions as option, idx}
152
+ <li
153
+ class="spectrum-Menu-item"
154
+ class:is-selected={isOptionSelected(getOptionValue(option, idx))}
155
+ role="option"
156
+ aria-selected="true"
157
+ tabindex="0"
158
+ on:click={() => onSelectOption(getOptionValue(option, idx))}
159
+ >
160
+ {#if getOptionIcon(option, idx)}
161
+ <span class="icon-Padding">
162
+ <img
163
+ src={getOptionIcon(option, idx)}
164
+ alt="icon"
165
+ width="20"
166
+ height="15"
167
+ />
168
+ </span>
169
+ {/if}
170
+ <span class="spectrum-Menu-itemLabel">
171
+ {getOptionLabel(option, idx)}
172
+ </span>
173
+ <svg
174
+ class="spectrum-Icon spectrum-UIIcon-Checkmark100 spectrum-Menu-checkmark spectrum-Menu-itemIcon"
175
+ focusable="false"
176
+ aria-hidden="true"
177
+ >
178
+ <use xlink:href="#spectrum-css-icon-Checkmark100" />
179
+ </svg>
180
+ </li>
181
+ {/each}
182
+ {/if}
183
+ </ul>
184
+ </div>
185
+ {/if}
186
+
187
+ <style>
188
+ .spectrum-Popover {
189
+ max-height: 240px;
190
+ z-index: 999;
191
+ top: 100%;
192
+ }
193
+ .spectrum-Popover:not(.auto-width) {
194
+ width: 100%;
195
+ }
196
+ .spectrum-Popover.auto-width :global(.spectrum-Menu-itemLabel) {
197
+ white-space: nowrap;
198
+ }
199
+ .spectrum-Picker {
200
+ width: 100%;
201
+ }
202
+ .spectrum-Picker-label:not(.auto-width) {
203
+ overflow: hidden;
204
+ text-overflow: ellipsis;
205
+ white-space: nowrap;
206
+ width: 0;
207
+ }
208
+ .placeholder {
209
+ font-style: italic;
210
+ }
211
+ .spectrum-Picker-label.auto-width.is-placeholder {
212
+ padding-right: 2px;
213
+ }
214
+
215
+ .icon-Padding {
216
+ padding-right: 10px;
217
+ }
218
+ .icon-Placeholder-Padding {
219
+ padding-top: 5px;
220
+ padding-right: 10px;
221
+ }
222
+ .spectrum-Popover :global(.spectrum-Search) {
223
+ margin-top: -1px;
224
+ margin-left: -1px;
225
+ width: calc(100% + 2px);
226
+ }
227
+ .spectrum-Popover :global(.spectrum-Search input) {
228
+ height: auto;
229
+ border-bottom-left-radius: 0;
230
+ border-bottom-right-radius: 0;
231
+ padding-top: var(--spectrum-global-dimension-size-100);
232
+ padding-bottom: var(--spectrum-global-dimension-size-100);
233
+ }
234
+ .spectrum-Popover :global(.spectrum-Search .spectrum-ClearButton) {
235
+ right: 1px;
236
+ top: 2px;
237
+ }
238
+ .spectrum-Popover :global(.spectrum-Search .spectrum-Textfield-icon) {
239
+ top: 9px;
240
+ }
241
+ </style>