@brickflow/ui 0.0.55 → 0.0.56

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/dist/module.json CHANGED
@@ -4,7 +4,7 @@
4
4
  },
5
5
  "configKey": "brickflowUi",
6
6
  "name": "@brickflow/ui",
7
- "version": "0.0.55",
7
+ "version": "0.0.56",
8
8
  "builder": {
9
9
  "@nuxt/module-builder": "1.0.3",
10
10
  "unbuild": "3.6.1"
@@ -21,11 +21,19 @@
21
21
  <div class="flex flex-wrap items-center gap-3">
22
22
  <button
23
23
  type="button"
24
- class="cursor-pointer rounded-xl bg-blue-500 px-3 py-2"
24
+ class="w-30 cursor-pointer rounded-xl px-3 py-2"
25
+ :class="stateActive ? 'bg-blue-500' : 'bg-blue-800'"
25
26
  @click="stateActive = !stateActive"
26
27
  >
27
28
  {{ stateActive ? 'On' : 'Off' }}
28
29
  </button>
30
+ <Button
31
+ :loading="stateActive"
32
+ :icon="firstIconName"
33
+ >
34
+ Loading
35
+ </Button>
36
+ <Button :loading="stateActive">Loading</Button>
29
37
  <div class="basis-full">
30
38
  <div class="relative flex w-fit flex-col items-start gap-3 pl-13">
31
39
  <div class="absolute top-0 left-0 z-0 size-full object-cover pt-7 pl-13">
@@ -0,0 +1,5 @@
1
+ <template>
2
+ <form class="flex flex-col gap-4">
3
+ <slot />
4
+ </form>
5
+ </template>
@@ -0,0 +1,57 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Email & Password',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import { ref } from 'vue'
9
+
10
+ import Button from '../Button/index.vue'
11
+ import Input from '../Input/index.vue'
12
+ import Form from './index.vue'
13
+
14
+ const email = ref('')
15
+ const password = ref('')
16
+ const submittedEmail = ref('')
17
+
18
+ function handleSubmit(): void {
19
+ submittedEmail.value = email.value
20
+ }
21
+ </script>
22
+
23
+ <template>
24
+ <Form
25
+ class="max-w-sm"
26
+ @submit.prevent="handleSubmit"
27
+ >
28
+ <Input
29
+ :value="email"
30
+ label="Email"
31
+ placeholder="example@brickflow.com"
32
+ type="email"
33
+ autocomplete="email"
34
+ @input="email = $event"
35
+ />
36
+ <Input
37
+ :value="password"
38
+ label="Password"
39
+ type="password"
40
+ autocomplete="current-password"
41
+ @input="password = $event"
42
+ />
43
+ <Button
44
+ type="submit"
45
+ class="self-start"
46
+ >
47
+ Sign in
48
+ </Button>
49
+ <p
50
+ v-if="submittedEmail"
51
+ role="status"
52
+ class="text-sm"
53
+ >
54
+ Submitted for {{ submittedEmail }}
55
+ </p>
56
+ </Form>
57
+ </template>
@@ -41,6 +41,7 @@
41
41
  const props = withDefaults(
42
42
  defineProps<{
43
43
  align?: 'center' | 'left' | 'right'
44
+ autocomplete?: string
44
45
  autoWidth?: boolean
45
46
  description?: string
46
47
  disabled?: boolean
@@ -62,6 +63,7 @@
62
63
  }>(),
63
64
  {
64
65
  align: 'left',
66
+ autocomplete: undefined,
65
67
  autoWidth: false,
66
68
  description: undefined,
67
69
  disabled: false,
@@ -245,6 +247,7 @@
245
247
  :value="currentValue"
246
248
  :type="inputType"
247
249
  :inputmode="props.type === 'password' ? undefined : props.type"
250
+ :autocomplete="props.autocomplete"
248
251
  :disabled="props.disabled"
249
252
  :readonly="props.readonly"
250
253
  :placeholder="props.placeholder"
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ description: 'Denominator limits decimal places: 0 for integers, 2 for up to two decimal digits.',
4
+ title: 'Denominator',
5
+ }
6
+ </script>
7
+
8
+ <script lang="ts" setup>
9
+ import { ref } from 'vue'
10
+
11
+ import InputNumber from './index.vue'
12
+
13
+ const price = ref<number>(8.55)
14
+ const price2 = ref<number>(12.55)
15
+ </script>
16
+
17
+ <template>
18
+ <div class="flex max-w-sm flex-col gap-4">
19
+ <InputNumber
20
+ :value="price"
21
+ :denominator="0"
22
+ label="Denominator: 0"
23
+ :description="`Value: ${price}`"
24
+ :min="-99"
25
+ :max="200"
26
+ @input="price = $event"
27
+ ></InputNumber>
28
+ <InputNumber
29
+ :value="price2"
30
+ :denominator="2"
31
+ label="Denominator: 2"
32
+ :description="`Value: ${price2}`"
33
+ :min="-99"
34
+ :max="200"
35
+ @input="price2 = $event"
36
+ ></InputNumber>
37
+ </div>
38
+ </template>
@@ -8,37 +8,62 @@
8
8
 
9
9
  const props = withDefaults(
10
10
  defineProps<{
11
+ align?: InputProps['align']
12
+ autocomplete?: string
13
+ autoWidth?: boolean
11
14
  denominator?: number
12
15
  description?: string
13
16
  disabled?: boolean
17
+ icon?: string
14
18
  label?: string
15
19
  max?: number
16
20
  min?: number
17
- nullable?: T
21
+ nullable?: boolean & T
18
22
  onBlur?: (value: true extends NoInfer<T> ? null | number : number) => void
23
+ onChange?: (value: string, target: HTMLInputElement) => void
19
24
  onFocus?: (value: true extends NoInfer<T> ? null | number : number) => void
25
+ onIconClick?: () => void
20
26
  onInput?: (value: true extends NoInfer<T> ? null | number : number) => void
21
27
  onKeydown?: (event: KeyboardEvent) => void
28
+ onTrailingIconClick?: () => void
29
+ placeholder?: string
30
+ readonly?: boolean
31
+ size?: InputProps['size']
22
32
  step?: number
33
+ trailingIcon?: string
23
34
  value: true extends NoInfer<T> ? null | number : number
35
+ variant?: InputProps['variant']
24
36
  }>(),
25
37
  {
38
+ align: undefined,
39
+ autocomplete: undefined,
40
+ autoWidth: false,
26
41
  denominator: 0,
27
42
  description: undefined,
28
43
  disabled: false,
44
+ icon: undefined,
29
45
  label: undefined,
30
46
  max: undefined,
31
47
  min: undefined,
32
48
  nullable: undefined,
33
49
  onBlur: undefined,
50
+ onChange: undefined,
34
51
  onFocus: undefined,
52
+ onIconClick: undefined,
35
53
  onInput: undefined,
36
54
  onKeydown: undefined,
37
- onValueChange: undefined,
55
+ onTrailingIconClick: undefined,
56
+ placeholder: undefined,
57
+ readonly: false,
58
+ size: undefined,
38
59
  step: 1,
60
+ trailingIcon: undefined,
61
+ variant: undefined,
39
62
  },
40
63
  )
41
64
 
65
+ type InputProps = InstanceType<typeof Input>['$props']
66
+
42
67
  const inputValue = shallowRef(prepareValue(props.value))
43
68
 
44
69
  watch(
@@ -52,11 +77,25 @@
52
77
  if (!target) {
53
78
  return
54
79
  }
80
+ if (props.nullable && target.value === '') {
81
+ inputValue.value = ''
82
+ props.onBlur?.(null as typeof props.value)
83
+ return
84
+ }
55
85
  const nextValue = prepareValue(props.value)
56
86
  target.value = nextValue
57
87
  inputValue.value = nextValue
58
88
  props.onBlur?.(props.value)
59
89
  }
90
+
91
+ function handleChange(value: string, target: HTMLInputElement): void {
92
+ props.onChange?.(value, target)
93
+ }
94
+
95
+ function handleFocus(): void {
96
+ props.onFocus?.(props.value)
97
+ }
98
+
60
99
  const allowedRegexp = /^-?\d*(?:\.\d*)?$/
61
100
  const validNumberRegexp = /^-?(?:\d+(?:\.\d+)?|\.\d+)$/
62
101
 
@@ -64,19 +103,36 @@
64
103
  if (!target) {
65
104
  return
66
105
  }
67
- const nextValue = rawValue.replace(/,/, '.')
106
+ let nextValue = rawValue.replace(/,/, '.')
68
107
  target.value = nextValue
69
108
 
70
109
  if (isEmpty(nextValue)) {
110
+ if (props.nullable) {
111
+ inputValue.value = ''
112
+ props.onInput?.(null as typeof props.value)
113
+ }
71
114
  return
72
115
  }
73
116
  if (!allowedRegexp.test(nextValue)) {
74
117
  target.value = inputValue.value
75
118
  return
76
119
  }
77
- if (!validNumberRegexp.test(nextValue)) {
120
+ const fraction = nextValue.split('.')[1]
121
+ if (fraction !== undefined && (props.denominator === 0 || fraction.length > props.denominator)) {
122
+ nextValue =
123
+ props.denominator === 0
124
+ ? nextValue.slice(0, nextValue.indexOf('.'))
125
+ : nextValue.slice(0, nextValue.indexOf('.') + props.denominator + 1)
126
+ target.value = nextValue
127
+ }
128
+ if (isEmpty(nextValue) || !validNumberRegexp.test(nextValue)) {
129
+ if (isEmpty(nextValue) && props.nullable) {
130
+ inputValue.value = ''
131
+ props.onInput?.(null as typeof props.value)
132
+ }
78
133
  return
79
134
  }
135
+
80
136
  let nextValueNumber = Number(nextValue)
81
137
  if (!isEmpty(props.min) && nextValueNumber < props.min) {
82
138
  target.value = prepareValue(props.min)
@@ -86,8 +142,41 @@
86
142
  target.value = prepareValue(props.max)
87
143
  nextValueNumber = props.max
88
144
  }
145
+
89
146
  props.onInput?.(nextValueNumber)
90
147
  }
148
+
149
+ function handleKeydown(event: KeyboardEvent): void {
150
+ props.onKeydown?.(event)
151
+ if (event.defaultPrevented || (event.key !== 'ArrowUp' && event.key !== 'ArrowDown')) {
152
+ return
153
+ }
154
+ const target = event.target
155
+ if (!(target instanceof HTMLInputElement) || target.disabled || target.readOnly) {
156
+ return
157
+ }
158
+ if (!Number.isFinite(props.step) || props.step <= 0) {
159
+ return
160
+ }
161
+
162
+ event.preventDefault()
163
+ const parsedValue = Number(target.value)
164
+ const currentValue = target.value !== '' && Number.isFinite(parsedValue) ? parsedValue : (props.value ?? 0)
165
+ const direction = event.key === 'ArrowUp' ? 1 : -1
166
+ const step = props.step * (event.shiftKey ? 2 : 1)
167
+ const precision = Math.max(0, Math.min(100, Math.trunc(props.denominator)))
168
+ let nextValue = Number((currentValue + direction * step).toFixed(precision))
169
+ if (!isEmpty(props.min) && nextValue < props.min) {
170
+ nextValue = props.min
171
+ }
172
+ if (!isEmpty(props.max) && nextValue > props.max) {
173
+ nextValue = props.max
174
+ }
175
+
176
+ target.value = prepareValue(nextValue)
177
+ props.onInput?.(nextValue)
178
+ }
179
+
91
180
  function prepareValue(value: typeof props.value): string {
92
181
  return value === null ? '' : String(value)
93
182
  }
@@ -97,15 +186,28 @@
97
186
  <Input
98
187
  v-bind="{
99
188
  ...$attrs,
100
- // onBlur: handleBlur,
101
- // onKeydown: handleKeydown,
102
189
  }"
103
190
  :value="inputValue"
191
+ :align="props.align"
192
+ :autocomplete="props.autocomplete"
193
+ :autoWidth="props.autoWidth"
104
194
  :label="props.label"
105
195
  :description="props.description"
106
- type="decimal"
196
+ :disabled="props.disabled"
197
+ :icon="props.icon"
198
+ :placeholder="props.placeholder"
199
+ :readonly="props.readonly"
200
+ :size="props.size"
201
+ :trailingIcon="props.trailingIcon"
202
+ :type="props.denominator === 0 ? 'numeric' : 'decimal'"
203
+ :variant="props.variant"
107
204
  @input="handleInput"
205
+ @change="handleChange"
108
206
  @blur="handleBlur"
207
+ @focus="handleFocus"
208
+ @iconClick="props.onIconClick?.()"
209
+ @keydown="handleKeydown"
210
+ @trailingIconClick="props.onTrailingIconClick?.()"
109
211
  >
110
212
  <template
111
213
  v-if="$slots.label"
@@ -0,0 +1,39 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Min & Max',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import { ref } from 'vue'
9
+
10
+ import InputNumber from './index.vue'
11
+
12
+ const price1 = ref<number>(25)
13
+ const price2 = ref<number>(-10)
14
+ </script>
15
+
16
+ <template>
17
+ <div class="flex max-w-sm flex-col gap-4">
18
+ <InputNumber
19
+ :value="price1"
20
+ label="Min: 0 — Max: 200"
21
+ :description="`Value: ${price1}`"
22
+ :min="0"
23
+ :max="200"
24
+ @input="price1 = $event"
25
+ >
26
+ <template #leading><span class="text-green-400">$</span></template>
27
+ </InputNumber>
28
+ <InputNumber
29
+ :value="price2"
30
+ label="Min: -100 — Max: 199"
31
+ :description="`Value: ${price2}`"
32
+ :min="-100"
33
+ :max="199"
34
+ @input="price2 = $event"
35
+ >
36
+ <template #leading><span class="text-green-400">$</span></template>
37
+ </InputNumber>
38
+ </div>
39
+ </template>
@@ -0,0 +1,25 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ title: 'Nullable',
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts" setup>
8
+ import { ref } from 'vue'
9
+
10
+ import InputNumber from './index.vue'
11
+
12
+ const value = ref<null | number>(null)
13
+ </script>
14
+
15
+ <template>
16
+ <div class="flex max-w-sm flex-col gap-4">
17
+ <InputNumber
18
+ :value="value"
19
+ nullable
20
+ label="Nullable (clear to set null)"
21
+ :description="`Value: ${value === null ? 'null' : value}`"
22
+ @input="value = $event"
23
+ />
24
+ </div>
25
+ </template>
@@ -0,0 +1,38 @@
1
+ <script lang="ts">
2
+ export const uiDemo = {
3
+ description: 'Step sets the change per arrow key press. Hold Shift to double it.',
4
+ title: 'Step',
5
+ }
6
+ </script>
7
+
8
+ <script lang="ts" setup>
9
+ import { ref } from 'vue'
10
+
11
+ import InputNumber from './index.vue'
12
+
13
+ const quantity = ref<number>(2)
14
+ const price = ref<number>(25)
15
+ </script>
16
+
17
+ <template>
18
+ <div class="flex max-w-sm flex-col gap-4">
19
+ <InputNumber
20
+ :value="quantity"
21
+ :step="1"
22
+ label="Step: 1"
23
+ :min="0"
24
+ :max="200"
25
+ :description="`Value: ${quantity}`"
26
+ @input="quantity = $event"
27
+ />
28
+ <InputNumber
29
+ :value="price"
30
+ :step="5"
31
+ label="Step: 5"
32
+ :min="-10"
33
+ :max="200"
34
+ :description="`Value: ${price}`"
35
+ @input="price = $event"
36
+ />
37
+ </div>
38
+ </template>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brickflow/ui",
3
- "version": "0.0.55",
3
+ "version": "0.0.56",
4
4
  "type": "module",
5
5
  "description": "brickflow UI Nuxt module.",
6
6
  "files": [
@@ -1,33 +0,0 @@
1
- <script lang="ts">
2
- export const uiDemo = {
3
- title: 'Number & Bounds',
4
- }
5
- </script>
6
-
7
- <script lang="ts" setup>
8
- import { ref } from 'vue'
9
-
10
- import InputNumber from './index.vue'
11
-
12
- const price = ref<number>(25)
13
- function handleInput(event: number): void {
14
- price.value = event
15
- }
16
- </script>
17
-
18
- <template>
19
- <div class="flex max-w-sm flex-col gap-4">
20
- <InputNumber
21
- :value="price"
22
- label="Price"
23
- description="Min: 0 — Max: 200"
24
- :min="0"
25
- :max="200"
26
- :step="5"
27
- @input="handleInput"
28
- >
29
- <template #leading><span class="text-green-400">$</span></template>
30
- </InputNumber>
31
- <p class="text-gray-500">Value: {{ price }}</p>
32
- </div>
33
- </template>