@budibase/bbui 3.39.22 → 3.39.23

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": "3.39.22",
4
+ "version": "3.39.23",
5
5
  "license": "MPL-2.0",
6
6
  "module": "dist/bbui.mjs",
7
7
  "exports": {
@@ -105,5 +105,5 @@
105
105
  }
106
106
  }
107
107
  },
108
- "gitHead": "5e71f6c90d0cb25ca4b7841065824284f1e59554"
108
+ "gitHead": "833e219692f2ec7892cbeaef8aeaa2732d1ccb27"
109
109
  }
@@ -1,7 +1,7 @@
1
- <script>
1
+ <script lang="ts">
2
2
  import "@spectrum-css/label/dist/index-vars.css"
3
3
 
4
- export let size = "M"
4
+ export let size: "S" | "M" | "L" = "M"
5
5
  export let grey = false
6
6
  export let red = false
7
7
  export let orange = false
@@ -11,7 +11,17 @@
11
11
  export let active = false
12
12
  export let inactive = false
13
13
  export let hoverable = false
14
- export let outlineColor = null
14
+ export let outlineColor: string | null = null
15
+ export let backgroundColor: string | null = null
16
+ export let textColor: string | null = null
17
+
18
+ $: customStyles = [
19
+ outlineColor ? `border: 2px solid ${outlineColor}` : "",
20
+ backgroundColor ? `background-color: ${backgroundColor}` : "",
21
+ textColor ? `color: ${textColor}` : "",
22
+ ]
23
+ .filter(Boolean)
24
+ .join("; ")
15
25
  </script>
16
26
 
17
27
  <!-- svelte-ignore a11y-no-static-element-interactions -->
@@ -30,7 +40,7 @@
30
40
  class:spectrum-Label--seafoam={seafoam}
31
41
  class:spectrum-Label--active={active}
32
42
  class:spectrum-Label--inactive={inactive}
33
- style={outlineColor ? `border: 2px solid ${outlineColor}` : ""}
43
+ style={customStyles}
34
44
  >
35
45
  <slot />
36
46
  </span>
@@ -5,9 +5,11 @@
5
5
  import NumberInput from "./NumberInput.svelte"
6
6
  import { createEventDispatcher } from "svelte"
7
7
  import Icon from "../../../Icon/Icon.svelte"
8
+ import { resolveTranslationGroup } from "@budibase/shared-core"
8
9
 
9
10
  export let value
10
11
  export let startDayOfWeek = "Monday"
12
+ export let calendarLabels = resolveTranslationGroup("calendar")
11
13
 
12
14
  const dispatch = createEventDispatcher()
13
15
  const DaysOfWeek = [
@@ -49,6 +51,10 @@
49
51
  $: calendarDate = dayjs(value || dayjs()).startOf("month")
50
52
  $: dayHeaders = getDayHeaders(startDayOfWeek)
51
53
  $: weekStarts = getWeekStarts(calendarDate, DayIndex[startDayOfWeek])
54
+ $: monthOptions = MonthsOfYear.map((month, idx) => ({
55
+ label: getTranslatedMonth(month),
56
+ value: idx,
57
+ }))
52
58
 
53
59
  const getWeekStarts = (monthStart, startDayIndex) => {
54
60
  if (!monthStart?.isValid()) {
@@ -74,6 +80,20 @@
74
80
  return [...DaysOfWeek.slice(index), ...DaysOfWeek.slice(0, index)]
75
81
  }
76
82
 
83
+ const getTranslatedDay = day => {
84
+ return calendarLabels?.[day.toLowerCase()] ?? day
85
+ }
86
+
87
+ const getTranslatedMonth = month => {
88
+ return calendarLabels?.[month.toLowerCase()] ?? month
89
+ }
90
+
91
+ const getDateTitle = date => {
92
+ return `${getTranslatedDay(date.format("dddd"))}, ${getTranslatedMonth(
93
+ date.format("MMMM")
94
+ )} ${date.date()}, ${date.year()}`
95
+ }
96
+
77
97
  const handleCalendarYearChange = e => {
78
98
  calendarDate = calendarDate.year(parseInt(e.target.value))
79
99
  }
@@ -108,8 +128,8 @@
108
128
  <div class="month-selector">
109
129
  <Select
110
130
  autoWidth
111
- placeholder={null}
112
- options={MonthsOfYear.map((m, idx) => ({ label: m, value: idx }))}
131
+ placeholder={calendarLabels?.datePickerPlaceholder}
132
+ options={monthOptions}
113
133
  value={calendarDate.month()}
114
134
  on:change={e => (calendarDate = calendarDate.month(e.detail))}
115
135
  />
@@ -150,8 +170,11 @@
150
170
  <tr>
151
171
  {#each dayHeaders as day}
152
172
  <th scope="col" class="spectrum-Calendar-tableCell">
153
- <abbr class="spectrum-Calendar-dayOfWeek" title={day}>
154
- {day[0]}
173
+ <abbr
174
+ class="spectrum-Calendar-dayOfWeek"
175
+ title={getTranslatedDay(day)}
176
+ >
177
+ {getTranslatedDay(day)[0]}
155
178
  </abbr>
156
179
  </th>
157
180
  {/each}
@@ -168,7 +191,7 @@
168
191
  aria-disabled="true"
169
192
  aria-selected="false"
170
193
  aria-invalid="false"
171
- title={date.format("dddd, MMMM D, YYYY")}
194
+ title={getDateTitle(date)}
172
195
  on:click={() => handleDateChange(date)}
173
196
  >
174
197
  <span
@@ -77,6 +77,7 @@
77
77
  }
78
78
  .spectrum-Datepicker {
79
79
  width: 100%;
80
+ min-width: 104px;
80
81
  overflow: hidden;
81
82
  }
82
83
  .spectrum-Datepicker .spectrum-Textfield {
@@ -10,6 +10,7 @@
10
10
  import { PopoverAlignment } from "../../../constants"
11
11
  import type dayjs from "dayjs"
12
12
  import { getLocaleStartDayOfWeek, type Weekday } from "./utils"
13
+ import { resolveTranslationGroup } from "@budibase/shared-core"
13
14
 
14
15
  export let id = null
15
16
  export let disabled = false
@@ -26,6 +27,7 @@
26
27
  export let align: PopoverAlignment = PopoverAlignment.Left
27
28
  const browserStartDayOfWeek = getLocaleStartDayOfWeek()
28
29
  export let startDayOfWeek: Weekday | undefined = undefined
30
+ export let calendarLabels = resolveTranslationGroup("calendar")
29
31
 
30
32
  let isOpen = false
31
33
  let anchor: HTMLElement
@@ -87,6 +89,7 @@
87
89
  {enableTime}
88
90
  {timeOnly}
89
91
  startDayOfWeek={resolvedStartDayOfWeek}
92
+ {calendarLabels}
90
93
  value={parsedValue}
91
94
  on:change
92
95
  />
@@ -5,6 +5,7 @@
5
5
  import ActionButton from "../../../ActionButton/ActionButton.svelte"
6
6
  import { createEventDispatcher, onMount } from "svelte"
7
7
  import { stringifyDate } from "../../../helpers"
8
+ import { resolveTranslationGroup } from "@budibase/shared-core"
8
9
 
9
10
  export let useKeyboardShortcuts = true
10
11
  export let ignoreTimezones
@@ -12,6 +13,7 @@
12
13
  export let timeOnly
13
14
  export let value
14
15
  export let startDayOfWeek = "Monday"
16
+ export let calendarLabels = resolveTranslationGroup("calendar")
15
17
 
16
18
  const dispatch = createEventDispatcher()
17
19
  let calendar
@@ -57,6 +59,7 @@
57
59
  <Calendar
58
60
  {value}
59
61
  {startDayOfWeek}
62
+ {calendarLabels}
60
63
  on:change={e => handleChange(e.detail)}
61
64
  bind:this={calendar}
62
65
  />
@@ -71,10 +74,12 @@
71
74
  size="S"
72
75
  on:click={() => dispatch("change", null)}
73
76
  >
74
- Clear
77
+ {calendarLabels?.datePickerClear || "Clear"}
75
78
  </ActionButton>
76
79
  <ActionButton size="S" on:click={setToNow}>
77
- {showTime ? "Now" : "Today"}
80
+ {showTime
81
+ ? calendarLabels?.datePickerNow || "Now"
82
+ : calendarLabels?.todayButton || "Today"}
78
83
  </ActionButton>
79
84
  </div>
80
85
  </div>
@@ -1,10 +1,12 @@
1
1
  <script>
2
2
  export let value
3
- export let min
4
- export let max
3
+ export let min = undefined
4
+ export let max = undefined
5
5
  export let hideArrows = false
6
- export let width
6
+ export let width = undefined
7
7
  export let type = "number"
8
+ export let disabled = false
9
+ export let readonly = false
8
10
 
9
11
  $: style = width ? `width:${width}px;` : ""
10
12
 
@@ -18,6 +20,8 @@
18
20
  {value}
19
21
  {min}
20
22
  {max}
23
+ {disabled}
24
+ {readonly}
21
25
  on:click={selectAll}
22
26
  on:change
23
27
  on:input
@@ -1,21 +1,34 @@
1
- <script>
1
+ <script lang="ts">
2
+ import type { Dayjs } from "dayjs"
2
3
  import dayjs from "dayjs"
3
4
  import NumberInput from "./NumberInput.svelte"
4
5
  import { createEventDispatcher } from "svelte"
5
6
 
6
- export let value
7
+ export let value: Dayjs | undefined
8
+ export let disableClearing = false
9
+ export let disabled = false
10
+ export let readonly = false
7
11
 
8
- const dispatch = createEventDispatcher()
12
+ const dispatch = createEventDispatcher<{ change: Dayjs | undefined }>()
9
13
 
10
14
  $: displayValue = value?.format("HH:mm")
11
15
 
12
- const handleChange = e => {
13
- if (!e.target.value) {
16
+ const handleChange = async (e: Event) => {
17
+ if (disabled || readonly) {
18
+ return
19
+ }
20
+
21
+ const target = e.target as HTMLInputElement
22
+ if (!target.value) {
23
+ if (disableClearing) {
24
+ target.value = displayValue || "00:00"
25
+ return
26
+ }
14
27
  dispatch("change", undefined)
15
28
  return
16
29
  }
17
30
 
18
- const [hour, minute] = e.target.value.split(":").map(x => parseInt(x))
31
+ const [hour, minute] = target.value.split(":").map(x => parseInt(x))
19
32
  dispatch(
20
33
  "change",
21
34
  (value || dayjs()).hour(hour).minute(minute).second(0).millisecond(0)
@@ -28,6 +41,8 @@
28
41
  hideArrows
29
42
  type={"time"}
30
43
  value={displayValue}
44
+ {disabled}
45
+ {readonly}
31
46
  on:input={handleChange}
32
47
  on:change={handleChange}
33
48
  />
@@ -6,12 +6,14 @@
6
6
  import { parseDate } from "../../helpers"
7
7
  import { writable } from "svelte/store"
8
8
  import { getLocaleStartDayOfWeek, type Weekday } from "./DatePicker/utils"
9
+ import { resolveTranslationGroup } from "@budibase/shared-core"
9
10
 
10
11
  export let enableTime: boolean | undefined = false
11
12
  export let timeOnly: boolean | undefined = false
12
13
  export let ignoreTimezones: boolean | undefined = false
13
14
  const browserStartDayOfWeek = getLocaleStartDayOfWeek()
14
15
  export let startDayOfWeek: Weekday | undefined = undefined
16
+ export let calendarLabels = resolveTranslationGroup("calendar")
15
17
  export let value: string[] | undefined = []
16
18
 
17
19
  const dispatch = createEventDispatcher()
@@ -77,6 +79,7 @@
77
79
  {timeOnly}
78
80
  {ignoreTimezones}
79
81
  startDayOfWeek={resolvedStartDayOfWeek}
82
+ {calendarLabels}
80
83
  />
81
84
  <div class="arrow">
82
85
  <Icon name="caret-right" />
@@ -88,6 +91,7 @@
88
91
  {timeOnly}
89
92
  {ignoreTimezones}
90
93
  startDayOfWeek={resolvedStartDayOfWeek}
94
+ {calendarLabels}
91
95
  />
92
96
  </div>
93
97
 
@@ -100,6 +100,6 @@
100
100
  }
101
101
  textarea {
102
102
  resize: vertical;
103
- min-height: 80px !important;
103
+ min-height: 38px !important;
104
104
  }
105
105
  </style>
@@ -3,6 +3,7 @@
3
3
  import DatePicker from "./Core/DatePicker/DatePicker.svelte"
4
4
  import { createEventDispatcher } from "svelte"
5
5
  import type { LabelPosition } from "../types"
6
+ import { resolveTranslationGroup } from "@budibase/shared-core"
6
7
 
7
8
  export let value: V | null = null
8
9
  export let label = undefined
@@ -16,6 +17,7 @@
16
17
  export let appendTo = undefined
17
18
  export let ignoreTimezones = false
18
19
  export let helpText = undefined
20
+ export let calendarLabels = resolveTranslationGroup("calendar")
19
21
 
20
22
  const dispatch = createEventDispatcher()
21
23
 
@@ -36,6 +38,7 @@
36
38
  {timeOnly}
37
39
  {appendTo}
38
40
  {ignoreTimezones}
41
+ {calendarLabels}
39
42
  on:change={onChange}
40
43
  />
41
44
  </Field>
@@ -2,6 +2,7 @@
2
2
  import Field from "./Field.svelte"
3
3
  import DateRangePicker from "./Core/DateRangePicker.svelte"
4
4
  import { createEventDispatcher } from "svelte"
5
+ import { resolveTranslationGroup } from "@budibase/shared-core"
5
6
 
6
7
  export let value = undefined
7
8
  export let label = null
@@ -14,6 +15,7 @@
14
15
  export let ignoreTimezones = false
15
16
  export let enableTime = false
16
17
  export let timeOnly = false
18
+ export let calendarLabels = resolveTranslationGroup("calendar")
17
19
 
18
20
  const dispatch = createEventDispatcher()
19
21
 
@@ -33,6 +35,7 @@
33
35
  {ignoreTimezones}
34
36
  {enableTime}
35
37
  {timeOnly}
38
+ {calendarLabels}
36
39
  on:change={onChange}
37
40
  />
38
41
  </Field>
@@ -0,0 +1,190 @@
1
+ <script lang="ts" module>
2
+ import type { LabelPosition } from "../types"
3
+
4
+ export type TimeFieldValue = string | null | undefined
5
+
6
+ export interface Props {
7
+ value?: TimeFieldValue
8
+ label?: string
9
+ labelPosition?: LabelPosition
10
+ disabled?: boolean
11
+ readonly?: boolean
12
+ error?: string | undefined | false
13
+ placeholder?: string | null
14
+ helpText?: string
15
+ required?: boolean
16
+ id?: string
17
+ name?: string
18
+ disableClearing?: boolean
19
+ onchange?: (value: string | undefined) => void
20
+ }
21
+ </script>
22
+
23
+ <script lang="ts">
24
+ import "@spectrum-css/textfield/dist/index-vars.css"
25
+ import dayjs from "dayjs"
26
+ import type { Dayjs } from "dayjs"
27
+ import TimePicker from "./Core/DatePicker/TimePicker.svelte"
28
+ import Field from "./Field.svelte"
29
+
30
+ let {
31
+ value = $bindable<TimeFieldValue>(undefined),
32
+ label = undefined,
33
+ labelPosition = "above",
34
+ disabled = false,
35
+ readonly = false,
36
+ error = undefined,
37
+ helpText = undefined,
38
+ required = false,
39
+ id = undefined,
40
+ name = undefined,
41
+ disableClearing = false,
42
+ onchange,
43
+ }: Props = $props()
44
+
45
+ const FALLBACK_TIME = "00:00"
46
+
47
+ const parseValue = (time: TimeFieldValue) => {
48
+ if (!time) {
49
+ return undefined
50
+ }
51
+
52
+ const [hour, minute] = time.split(":").map(part => Number(part))
53
+ if (
54
+ !Number.isInteger(hour) ||
55
+ !Number.isInteger(minute) ||
56
+ hour < 0 ||
57
+ hour > 23 ||
58
+ minute < 0 ||
59
+ minute > 59
60
+ ) {
61
+ return undefined
62
+ }
63
+
64
+ return dayjs().hour(hour).minute(minute).second(0).millisecond(0)
65
+ }
66
+
67
+ const handleChange = (event: CustomEvent<Dayjs | undefined>) => {
68
+ const nextValue = event.detail?.format("HH:mm")
69
+ if (!nextValue && disableClearing) {
70
+ return
71
+ }
72
+ value = nextValue
73
+ onchange?.(nextValue)
74
+ }
75
+
76
+ const parsedValue = $derived(parseValue(value))
77
+ let isFocused = $state(false)
78
+
79
+ $effect(() => {
80
+ if (disableClearing && !value) {
81
+ value = FALLBACK_TIME
82
+ onchange?.(value)
83
+ }
84
+ })
85
+ </script>
86
+
87
+ <Field {id} {helpText} {label} {labelPosition} {error} {required}>
88
+ <div
89
+ class="time-field spectrum-Textfield"
90
+ class:is-disabled={disabled}
91
+ class:is-readonly={readonly}
92
+ class:is-invalid={!!error}
93
+ class:is-focused={isFocused}
94
+ aria-disabled={disabled}
95
+ onfocusin={() => {
96
+ isFocused = true
97
+ }}
98
+ onfocusout={() => {
99
+ isFocused = false
100
+ }}
101
+ >
102
+ <TimePicker
103
+ value={parsedValue}
104
+ {disableClearing}
105
+ {disabled}
106
+ {readonly}
107
+ on:change={handleChange}
108
+ />
109
+ {#if name}
110
+ <input type="hidden" {name} value={value ?? ""} {disabled} />
111
+ {/if}
112
+ </div>
113
+ </Field>
114
+
115
+ <style>
116
+ .time-field {
117
+ display: inline-flex;
118
+ align-items: center;
119
+ min-width: 104px;
120
+ width: fit-content;
121
+ --spectrum-textfield-padding-bottom: var(--spectrum-textfield-padding-top);
122
+ }
123
+
124
+ .time-field.is-disabled,
125
+ .time-field.is-readonly {
126
+ opacity: 0.5;
127
+ pointer-events: none;
128
+ }
129
+
130
+ .time-field :global(.time-picker) {
131
+ width: 100%;
132
+ }
133
+
134
+ .time-field :global(input[type="time"]) {
135
+ background-color: var(
136
+ --spectrum-textfield-m-background-color,
137
+ var(--spectrum-global-color-gray-50)
138
+ );
139
+ border: var(--spectrum-textfield-border-size) solid
140
+ var(
141
+ --spectrum-textfield-m-border-color,
142
+ var(--spectrum-alias-border-color)
143
+ );
144
+ border-radius: var(--spectrum-textfield-border-radius);
145
+ box-sizing: border-box !important;
146
+ color: var(
147
+ --spectrum-textfield-m-text-color,
148
+ var(--spectrum-alias-text-color)
149
+ );
150
+ font-family: var(--font-sans);
151
+ font-size: var(--spectrum-textfield-text-size);
152
+ font-weight: var(--spectrum-regular-font-weight);
153
+ height: var(--spectrum-textfield-height);
154
+ line-height: var(--spectrum-textfield-text-line-height);
155
+ margin: 0;
156
+ min-width: 72px;
157
+ outline: none;
158
+ padding: var(--spectrum-textfield-padding-top)
159
+ calc(var(--spectrum-textfield-padding-x) + var(--spacing-xs))
160
+ var(--spectrum-textfield-padding-bottom) !important;
161
+ text-indent: var(--spacing-xs);
162
+ width: 100%;
163
+ }
164
+
165
+ .time-field :global(input[type="time"]:hover),
166
+ .time-field :global(input[type="time"]:focus) {
167
+ background-color: var(
168
+ --spectrum-textfield-m-background-color-hover,
169
+ var(--spectrum-global-color-gray-50)
170
+ );
171
+ border-color: var(
172
+ --spectrum-textfield-m-border-color-hover,
173
+ var(--spectrum-alias-border-color-hover)
174
+ );
175
+ }
176
+
177
+ .time-field :global(input[type="time"]:focus) {
178
+ border-color: var(
179
+ --spectrum-textfield-m-border-color-down,
180
+ var(--spectrum-alias-border-color-mouse-focus)
181
+ );
182
+ }
183
+
184
+ .time-field.is-invalid :global(input[type="time"]) {
185
+ border-color: var(
186
+ --spectrum-textfield-m-border-color-error,
187
+ var(--spectrum-semantic-negative-color-default)
188
+ );
189
+ }
190
+ </style>
package/src/index.ts CHANGED
@@ -24,6 +24,7 @@ export { default as Select } from "./Form/Select.svelte"
24
24
  export { default as Slider } from "./Form/Slider.svelte"
25
25
  export { default as Stepper } from "./Form/Stepper.svelte"
26
26
  export { default as TextArea } from "./Form/TextArea.svelte"
27
+ export { default as TimeField } from "./Form/TimeField.svelte"
27
28
  export { default as Toggle } from "./Form/Toggle.svelte"
28
29
 
29
30
  // Core form components to be used elsewhere (standard components)