Skip to content

Commit 0b1f0e9

Browse files
authored
Number field (#58)
1 parent f6535bf commit 0b1f0e9

File tree

15 files changed

+482
-9
lines changed

15 files changed

+482
-9
lines changed

packages/primitives/src/components/number-field/number-field-root.vue

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,14 @@ const inputMode = computed<HTMLAttributes['inputmode']>(() => {
9090
// Replace negative textValue formatted using currencySign: 'accounting'
9191
// with a textValue that can be announced using a minus sign.
9292
const textValueFormatter = useNumberFormatter(locale, formatOptions);
93-
const textValue = computed(() => (Number.isNaN(modelValue.value) ? '' : textValueFormatter.format(modelValue.value)));
93+
const textValue = computed(() => {
94+
if (Number.isNaN(modelValue.value)) return '';
95+
96+
const formatted = textValueFormatter.format(modelValue.value);
97+
if (formatted === 'NaN') return '';
98+
99+
return formatted;
100+
});
94101
95102
function validate(val: string) {
96103
return numberParser.isValidPartialNumber(val, min.value, max.value);

packages/primitives/src/components/number-field/shared.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { unrefElement, useEventListener } from '@vueuse/core';
44
import type { MaybeComputedElementRef } from '@vueuse/core';
55
import { createEventHook, isClient, reactiveComputed } from '@vueuse/shared';
66
import { NumberFormatter, NumberParser } from '@internationalized/number';
7+
import { isNullish } from '../../shared';
78

89
export function usePressedHold(options: { target?: MaybeComputedElementRef; disabled: Ref<boolean> }) {
910
const { disabled } = options;
@@ -68,7 +69,11 @@ export function useNumberParser(locale: Ref<string>, options: Ref<Intl.NumberFor
6869
return reactiveComputed(() => new NumberParser(locale.value, options.value));
6970
}
7071

71-
export function handleDecimalOperation(operator: '-' | '+', value1: number, value2: number): number {
72+
export function handleDecimalOperation(operator: '-' | '+', value1?: number, value2?: number): number {
73+
if (isNullish(value1) || isNullish(value2)) {
74+
return 0;
75+
}
76+
7277
let v1 = value1;
7378
let v2 = value2;
7479

packages/ui/src/components/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export * from './label';
2323
export * from './menu';
2424
export * from './menubar';
2525
export * from './navigation-menu';
26+
export * from './number-field';
2627
export * from './pagination';
2728
export * from './pin-input';
2829
export * from './popover';
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import SNumberFieldDecrement from './number-field-decrement.vue';
2+
import SNumberFieldIncrement from './number-field-increment.vue';
3+
import SNumberFieldInput from './number-field-input.vue';
4+
import SNumberFieldRoot from './number-field-root.vue';
5+
import SNumberField from './number-field.vue';
6+
7+
export { SNumberFieldDecrement, SNumberFieldIncrement, SNumberFieldInput, SNumberFieldRoot, SNumberField };
8+
9+
export * from './types';
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
import { NumberFieldDecrement, Slot } from '@soybean-ui/primitives';
4+
import { cn, numberFieldVariants } from '@soybean-ui/variants';
5+
import { Minus } from 'lucide-vue-next';
6+
import type { NumberFieldDecrementProps } from './types';
7+
8+
defineOptions({
9+
name: 'SNumberFieldDecrement'
10+
});
11+
12+
const { class: cls, size, center, iconClass, disabled } = defineProps<NumberFieldDecrementProps>();
13+
14+
const mergedCls = computed(() => {
15+
const { decrement, decrementIcon } = numberFieldVariants({ size, center });
16+
return {
17+
cls: cn(decrement(), cls),
18+
iconCls: cn(decrementIcon(), iconClass)
19+
};
20+
});
21+
</script>
22+
23+
<template>
24+
<NumberFieldDecrement :class="mergedCls.cls" data-slot="decrement" :disabled="disabled">
25+
<Slot :class="mergedCls.iconCls">
26+
<slot>
27+
<Minus />
28+
</slot>
29+
</Slot>
30+
</NumberFieldDecrement>
31+
</template>
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
import { NumberFieldIncrement, Slot } from '@soybean-ui/primitives';
4+
import { cn, numberFieldVariants } from '@soybean-ui/variants';
5+
import { Plus } from 'lucide-vue-next';
6+
import type { NumberFieldIncrementProps } from './types';
7+
8+
defineOptions({
9+
name: 'SNumberFieldIncrement'
10+
});
11+
12+
const { class: cls, size, center, iconClass, disabled } = defineProps<NumberFieldIncrementProps>();
13+
14+
const mergedCls = computed(() => {
15+
const { increment, incrementIcon } = numberFieldVariants({ size, center });
16+
17+
return {
18+
cls: cn(increment(), cls),
19+
iconCls: cn(incrementIcon(), iconClass)
20+
};
21+
});
22+
</script>
23+
24+
<template>
25+
<NumberFieldIncrement :class="mergedCls.cls" data-slot="increment" :disabled="disabled">
26+
<Slot :class="mergedCls.iconCls">
27+
<slot>
28+
<Plus />
29+
</slot>
30+
</Slot>
31+
</NumberFieldIncrement>
32+
</template>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
import { NumberFieldInput } from '@soybean-ui/primitives';
4+
import { cn, numberFieldVariants } from '@soybean-ui/variants';
5+
import type { NumberFieldInputProps } from './types';
6+
7+
defineOptions({
8+
name: 'SNumberFieldInput'
9+
});
10+
11+
const { class: cls, size, center } = defineProps<NumberFieldInputProps>();
12+
13+
const mergedCls = computed(() => {
14+
const { input } = numberFieldVariants({ size, center });
15+
16+
return cn(input(), cls);
17+
});
18+
</script>
19+
20+
<template>
21+
<NumberFieldInput :class="mergedCls" data-slot="input" />
22+
</template>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script setup lang="ts">
2+
import { computed } from 'vue';
3+
import { NumberFieldRoot, useForwardPropsEmits } from '@soybean-ui/primitives';
4+
import { cn, numberFieldVariants } from '@soybean-ui/variants';
5+
import type { NumberFieldRootEmits, NumberFieldRootProps } from './types';
6+
7+
defineOptions({
8+
name: 'SNumberFieldRoot'
9+
});
10+
11+
const { class: cls, size, ...delegatedProps } = defineProps<NumberFieldRootProps>();
12+
13+
const emit = defineEmits<NumberFieldRootEmits>();
14+
15+
const forwarded = useForwardPropsEmits(delegatedProps, emit);
16+
17+
const mergedCls = computed(() => {
18+
const { root } = numberFieldVariants({ size });
19+
20+
return cn(root(), cls);
21+
});
22+
</script>
23+
24+
<template>
25+
<NumberFieldRoot v-bind="forwarded" :class="mergedCls">
26+
<slot />
27+
</NumberFieldRoot>
28+
</template>
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<script setup lang="ts">
2+
import { useForwardPropsEmits } from '@soybean-ui/primitives';
3+
import NumberFieldDecrement from './number-field-decrement.vue';
4+
import NumberFieldIncrement from './number-field-increment.vue';
5+
import NumberFieldInput from './number-field-input.vue';
6+
import NumberFieldRoot from './number-field-root.vue';
7+
import type { NumberFieldEmits, NumberFieldProps } from './types';
8+
9+
defineOptions({
10+
name: 'SNumberField'
11+
});
12+
13+
const {
14+
class: cls,
15+
size,
16+
center,
17+
disabledDecrement,
18+
disabledIncrement,
19+
ui,
20+
...rootProps
21+
} = defineProps<NumberFieldProps>();
22+
23+
const emit = defineEmits<NumberFieldEmits>();
24+
25+
const forwarded = useForwardPropsEmits(rootProps, emit);
26+
</script>
27+
28+
<template>
29+
<NumberFieldRoot v-bind="forwarded" :class="cls || ui?.root" :size="size">
30+
<NumberFieldInput :class="ui?.input" :size="size" :center="center" />
31+
<NumberFieldDecrement
32+
:class="ui?.decrement"
33+
:size="size"
34+
:center="center"
35+
:icon-class="ui?.decrementIcon"
36+
:disabled="disabled || disabledDecrement"
37+
>
38+
<slot name="decrement-icon" />
39+
</NumberFieldDecrement>
40+
<NumberFieldIncrement
41+
:class="ui?.increment"
42+
:size="size"
43+
:center="center"
44+
:icon-class="ui?.incrementIcon"
45+
:disabled="disabled || disabledIncrement"
46+
>
47+
<slot name="increment-icon" />
48+
</NumberFieldIncrement>
49+
</NumberFieldRoot>
50+
</template>
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import type {
2+
ClassValue,
3+
NumberFieldRootEmits,
4+
NumberFieldDecrementProps as _NumberFieldDecrementProps,
5+
NumberFieldIncrementProps as _NumberFieldIncrementProps,
6+
NumberFieldInputProps as _NumberFieldInputProps,
7+
NumberFieldRootProps as _NumberFieldRootProps
8+
} from '@soybean-ui/primitives';
9+
import type { NumberFieldSlots, ThemeSize } from '@soybean-ui/variants';
10+
11+
export interface NumberFieldRootProps extends _NumberFieldRootProps {
12+
size?: ThemeSize;
13+
}
14+
15+
export interface NumberFieldInputProps extends _NumberFieldInputProps {
16+
size?: ThemeSize;
17+
center?: boolean;
18+
}
19+
20+
export interface NumberFieldDecrementProps extends _NumberFieldDecrementProps {
21+
size?: ThemeSize;
22+
center?: boolean;
23+
iconClass?: ClassValue;
24+
}
25+
26+
export interface NumberFieldIncrementProps extends _NumberFieldIncrementProps {
27+
size?: ThemeSize;
28+
center?: boolean;
29+
iconClass?: ClassValue;
30+
}
31+
32+
export type NumberFieldUi = Partial<Record<NumberFieldSlots, ClassValue>>;
33+
34+
export interface NumberFieldProps extends NumberFieldRootProps {
35+
center?: boolean;
36+
disabledDecrement?: boolean;
37+
disabledIncrement?: boolean;
38+
ui?: NumberFieldUi;
39+
}
40+
41+
export type NumberFieldEmits = NumberFieldRootEmits;
42+
43+
export type { NumberFieldRootEmits };

0 commit comments

Comments
 (0)