vueless 0.0.672 → 0.0.674
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/composables/useUI.ts +7 -7
- package/constants.js +1 -0
- package/package.json +1 -1
- package/types.ts +5 -2
- package/ui.dropdown-list/UDropdownList.vue +10 -1
- package/ui.form-label/ULabel.vue +3 -2
- package/ui.form-select/USelect.vue +5 -0
- package/ui.other-theme-color-toggle/config.ts +2 -1
- package/ui.text-badge/storybook/stories.ts +1 -1
- package/ui.text-empty/storybook/stories.ts +1 -1
- package/ui.text-file/storybook/stories.ts +1 -1
- package/ui.text-files/storybook/stories.ts +1 -1
- package/ui.text-money/UMoney.vue +23 -67
- package/ui.text-money/config.ts +12 -32
- package/ui.text-money/storybook/stories.ts +49 -52
- package/ui.text-money/types.ts +1 -1
- package/ui.text-notify/storybook/stories.ts +1 -1
- package/ui.text-number/UNumber.vue +76 -0
- package/ui.text-number/config.ts +42 -0
- package/ui.text-number/constants.ts +5 -0
- package/ui.text-number/storybook/docs.mdx +16 -0
- package/ui.text-number/storybook/stories.ts +105 -0
- package/ui.text-number/types.ts +82 -0
- package/{ui.text-money/utilMoney.ts → ui.text-number/utilNumber.ts} +13 -14
package/composables/useUI.ts
CHANGED
|
@@ -22,8 +22,8 @@ import type {
|
|
|
22
22
|
MutatedProps,
|
|
23
23
|
UnknownObject,
|
|
24
24
|
ComponentNames,
|
|
25
|
-
ComponentConfig,
|
|
26
25
|
NestedComponent,
|
|
26
|
+
ComponentConfigFull,
|
|
27
27
|
VuelessComponentInstance,
|
|
28
28
|
} from "../types.ts";
|
|
29
29
|
|
|
@@ -45,25 +45,25 @@ export default function useUI<T>(
|
|
|
45
45
|
? (parent?.type.__name as ComponentNames)
|
|
46
46
|
: (type.__name as ComponentNames);
|
|
47
47
|
|
|
48
|
-
const globalConfig = (vuelessConfig.component?.[componentName] || {}) as
|
|
48
|
+
const globalConfig = (vuelessConfig.component?.[componentName] || {}) as ComponentConfigFull<T>;
|
|
49
49
|
|
|
50
50
|
const vuelessStrategy = Object.values(STRATEGY_TYPE).includes(vuelessConfig.strategy || "")
|
|
51
51
|
? (vuelessConfig.strategy as Strategies)
|
|
52
52
|
: (STRATEGY_TYPE.merge as Strategies);
|
|
53
53
|
|
|
54
54
|
const firstClassKey = Object.keys(defaultConfig || {})[0];
|
|
55
|
-
const config = ref({}) as Ref<
|
|
55
|
+
const config = ref({}) as Ref<ComponentConfigFull<T>>;
|
|
56
56
|
const attrs = useAttrs();
|
|
57
57
|
|
|
58
58
|
watchEffect(() => {
|
|
59
|
-
const propsConfig = props.config as
|
|
59
|
+
const propsConfig = props.config as ComponentConfigFull<T>;
|
|
60
60
|
|
|
61
61
|
config.value = getMergedConfig({
|
|
62
62
|
defaultConfig,
|
|
63
63
|
globalConfig,
|
|
64
64
|
propsConfig,
|
|
65
65
|
vuelessStrategy,
|
|
66
|
-
}) as
|
|
66
|
+
}) as ComponentConfigFull<T>;
|
|
67
67
|
});
|
|
68
68
|
|
|
69
69
|
/**
|
|
@@ -73,7 +73,7 @@ export default function useUI<T>(
|
|
|
73
73
|
return computed(() => {
|
|
74
74
|
const mutatedPropsValue = toValue(mutatedProps);
|
|
75
75
|
const color = (toValue(mutatedProps || {}).color || props.color) as BrandColors;
|
|
76
|
-
const value = (config.value as
|
|
76
|
+
const value = (config.value as ComponentConfigFull<T>)[key];
|
|
77
77
|
|
|
78
78
|
let classes = "";
|
|
79
79
|
|
|
@@ -203,7 +203,7 @@ export default function useUI<T>(
|
|
|
203
203
|
function getExtendsKeyConfig(configKey: string) {
|
|
204
204
|
let extendsKeyConfig: NestedComponent = {};
|
|
205
205
|
|
|
206
|
-
const propsConfig = props.config as
|
|
206
|
+
const propsConfig = props.config as ComponentConfigFull<T>;
|
|
207
207
|
const extendsKeys = getExtendsKeys(config.value[configKey]);
|
|
208
208
|
|
|
209
209
|
if (extendsKeys.length) {
|
package/constants.js
CHANGED
package/package.json
CHANGED
package/types.ts
CHANGED
|
@@ -6,6 +6,7 @@ import UFilesDefaultConfig from "./ui.text-files/config.ts";
|
|
|
6
6
|
import UMoneyDefaultConfig from "./ui.text-money/config.ts";
|
|
7
7
|
import UHeaderDefaultConfig from "./ui.text-header/config.ts";
|
|
8
8
|
import UNotifyDefaultConfig from "./ui.text-notify/config.ts";
|
|
9
|
+
import UNumberDefaultConfig from "./ui.text-number/config.ts";
|
|
9
10
|
import UDotDefaultConfig from "./ui.other-dot/config.ts";
|
|
10
11
|
import UButtonDefaultConfig from "./ui.button/config.ts";
|
|
11
12
|
import ULinkDefaultConfig from "./ui.button-link/config.ts";
|
|
@@ -194,6 +195,7 @@ export interface Components {
|
|
|
194
195
|
UMoney: Partial<typeof UMoneyDefaultConfig>;
|
|
195
196
|
UHeader: Partial<typeof UHeaderDefaultConfig>;
|
|
196
197
|
UNotify: Partial<typeof UNotifyDefaultConfig>;
|
|
198
|
+
UNumber: Partial<typeof UNumberDefaultConfig>;
|
|
197
199
|
UDot: Partial<typeof UDotDefaultConfig>;
|
|
198
200
|
UButton: Partial<typeof UButtonDefaultConfig>;
|
|
199
201
|
ULink: Partial<typeof ULinkDefaultConfig>;
|
|
@@ -255,8 +257,9 @@ export type ComponentConfig<T> = Partial<{
|
|
|
255
257
|
? T[K]
|
|
256
258
|
: T[K] | string | UnknownObject
|
|
257
259
|
: never;
|
|
258
|
-
}> &
|
|
259
|
-
|
|
260
|
+
}> & { [key: string]: unknown };
|
|
261
|
+
|
|
262
|
+
export type ComponentConfigFull<T> = ComponentConfig<T> & NestedComponent;
|
|
260
263
|
|
|
261
264
|
export interface NestedComponent {
|
|
262
265
|
base?: string;
|
|
@@ -270,6 +270,7 @@ const {
|
|
|
270
270
|
tabindex="1"
|
|
271
271
|
:style="{ maxHeight: wrapperMaxHeight }"
|
|
272
272
|
v-bind="wrapperAttrs"
|
|
273
|
+
:data-test="`${dataTest}-list`"
|
|
273
274
|
@keydown.self.down.prevent="pointerForward"
|
|
274
275
|
@keydown.self.up.prevent="pointerBackward"
|
|
275
276
|
@keydown.enter.stop.self="addPointerElement('Enter')"
|
|
@@ -288,6 +289,7 @@ const {
|
|
|
288
289
|
<span
|
|
289
290
|
v-if="!(option && (option.groupLabel || option.isSubGroup)) && !option.isHidden"
|
|
290
291
|
v-bind="isSelectedOption(option) ? optionActiveAttrs : optionAttrs"
|
|
292
|
+
:data-test="`${dataTest}-option`"
|
|
291
293
|
:class="optionHighlight(index, option)"
|
|
292
294
|
@click="select(option), onClickOption(option)"
|
|
293
295
|
@mouseenter.self="pointerSet(index)"
|
|
@@ -353,6 +355,7 @@ const {
|
|
|
353
355
|
ref="add-option"
|
|
354
356
|
role="option"
|
|
355
357
|
v-bind="addOptionLabelWrapperAttrs"
|
|
358
|
+
:data-test="`${dataTest}-add`"
|
|
356
359
|
@click="onClickAddOption"
|
|
357
360
|
@mouseenter.self="pointerSet(options.length + 1)"
|
|
358
361
|
>
|
|
@@ -362,7 +365,13 @@ const {
|
|
|
362
365
|
</span>
|
|
363
366
|
</li>
|
|
364
367
|
|
|
365
|
-
<UButton
|
|
368
|
+
<UButton
|
|
369
|
+
round
|
|
370
|
+
square
|
|
371
|
+
v-bind="addOptionButtonAttrs"
|
|
372
|
+
:data-test="`${dataTest}-add-button`"
|
|
373
|
+
@click="onClickAddOption"
|
|
374
|
+
>
|
|
366
375
|
<UIcon
|
|
367
376
|
internal
|
|
368
377
|
color="white"
|
package/ui.form-label/ULabel.vue
CHANGED
|
@@ -80,9 +80,10 @@ const { wrapperAttrs, contentAttrs, labelAttrs, descriptionAttrs } = useUI<Confi
|
|
|
80
80
|
v-if="isHorizontalPlacement || isTopWithDescPlacement"
|
|
81
81
|
ref="wrapperRef"
|
|
82
82
|
v-bind="wrapperAttrs"
|
|
83
|
+
:data-test="dataTest"
|
|
83
84
|
@click="onClick"
|
|
84
85
|
>
|
|
85
|
-
<div v-bind="contentAttrs">
|
|
86
|
+
<div v-bind="contentAttrs" :data-test="`${dataTest}-content`">
|
|
86
87
|
<!-- @slot Use it to add label content. -->
|
|
87
88
|
<slot />
|
|
88
89
|
</div>
|
|
@@ -136,7 +137,7 @@ const { wrapperAttrs, contentAttrs, labelAttrs, descriptionAttrs } = useUI<Confi
|
|
|
136
137
|
</slot>
|
|
137
138
|
</label>
|
|
138
139
|
|
|
139
|
-
<div v-bind="contentAttrs">
|
|
140
|
+
<div v-bind="contentAttrs" :data-test="`${dataTest}-content`">
|
|
140
141
|
<!-- @slot Use it to add label content. -->
|
|
141
142
|
<slot />
|
|
142
143
|
</div>
|
|
@@ -517,6 +517,7 @@ const {
|
|
|
517
517
|
color="gray"
|
|
518
518
|
:name="config.defaults.dropdownIcon"
|
|
519
519
|
v-bind="toggleIconAttrs"
|
|
520
|
+
:data-test="`${dataTest}-toggle`"
|
|
520
521
|
:tabindex="-1"
|
|
521
522
|
/>
|
|
522
523
|
</slot>
|
|
@@ -538,6 +539,7 @@ const {
|
|
|
538
539
|
color="gray"
|
|
539
540
|
:name="config.defaults.clearIcon"
|
|
540
541
|
v-bind="clearIconAttrs"
|
|
542
|
+
:data-test="`${dataTest}-clear`"
|
|
541
543
|
/>
|
|
542
544
|
</slot>
|
|
543
545
|
</div>
|
|
@@ -612,6 +614,7 @@ const {
|
|
|
612
614
|
color="gray"
|
|
613
615
|
:name="config.defaults.clearMultipleIcon"
|
|
614
616
|
v-bind="clearMultipleIconAttrs"
|
|
617
|
+
:data-test="`${dataTest}-clear-all`"
|
|
615
618
|
/>
|
|
616
619
|
</slot>
|
|
617
620
|
</div>
|
|
@@ -631,6 +634,7 @@ const {
|
|
|
631
634
|
:disabled="disabled"
|
|
632
635
|
:aria-controls="'listbox-' + elementId"
|
|
633
636
|
v-bind="searchInputAttrs"
|
|
637
|
+
:data-test="`${dataTest}-search`"
|
|
634
638
|
@focus="activate"
|
|
635
639
|
@blur.prevent="deactivate"
|
|
636
640
|
@keyup.esc="deactivate"
|
|
@@ -689,6 +693,7 @@ const {
|
|
|
689
693
|
:add-option="addOption"
|
|
690
694
|
tabindex="-1"
|
|
691
695
|
v-bind="dropdownListAttrs as KeyAttrsWithConfig<UDropdownListConfig>"
|
|
696
|
+
:data-test="dataTest"
|
|
692
697
|
@add="onAddOption"
|
|
693
698
|
@focus="activate"
|
|
694
699
|
@mousedown.prevent.capture
|
package/ui.text-money/UMoney.vue
CHANGED
|
@@ -3,11 +3,11 @@ import { computed } from "vue";
|
|
|
3
3
|
|
|
4
4
|
import useUI from "../composables/useUI.ts";
|
|
5
5
|
import { getDefaults } from "../utils/ui.ts";
|
|
6
|
-
import { hasSlotContent } from "../utils/helper.ts";
|
|
7
6
|
|
|
8
7
|
import { COMPONENT_NAME } from "./constants.ts";
|
|
9
8
|
import defaultConfig from "./config.ts";
|
|
10
|
-
|
|
9
|
+
|
|
10
|
+
import UNumber from "../ui.text-number/UNumber.vue";
|
|
11
11
|
|
|
12
12
|
import type { Props, Config } from "./types.ts";
|
|
13
13
|
|
|
@@ -24,81 +24,37 @@ const currencySymbolPosition = computed(() => {
|
|
|
24
24
|
};
|
|
25
25
|
});
|
|
26
26
|
|
|
27
|
-
const currencySpace = computed(() => {
|
|
28
|
-
return props.symbolDivided ? " " : "";
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const mathSign = computed(() => {
|
|
32
|
-
let type = "";
|
|
33
|
-
|
|
34
|
-
if (props.sign === MONEY_SIGN_TYPE.unsigned) type = "";
|
|
35
|
-
if (props.sign === MONEY_SIGN_TYPE.positive) type = MATH_SIGN.PLUS;
|
|
36
|
-
if (props.sign === MONEY_SIGN_TYPE.negative) type = MATH_SIGN.MINUS;
|
|
37
|
-
if (props.sign === MONEY_SIGN_TYPE.auto && props.value < 0) type = MATH_SIGN.MINUS;
|
|
38
|
-
|
|
39
|
-
return type;
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
const preparedMoney = computed(() => {
|
|
43
|
-
return separatedMoney(
|
|
44
|
-
Math.abs(props.value || 0),
|
|
45
|
-
props.minFractionDigits,
|
|
46
|
-
props.maxFractionDigits,
|
|
47
|
-
props.decimalSeparator,
|
|
48
|
-
props.thousandsSeparator,
|
|
49
|
-
);
|
|
50
|
-
});
|
|
51
|
-
|
|
52
27
|
/**
|
|
53
28
|
* Get element / nested component attributes for each config token ✨
|
|
54
29
|
* Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
|
|
55
30
|
*/
|
|
56
|
-
const {
|
|
57
|
-
moneyAttrs,
|
|
58
|
-
sumAttrs,
|
|
59
|
-
mathSignAttrs,
|
|
60
|
-
integerAttrs,
|
|
61
|
-
pennyAttrs,
|
|
62
|
-
slotLeftAttrs,
|
|
63
|
-
symbolAttrs,
|
|
64
|
-
slotRightAttrs,
|
|
65
|
-
} = useUI<Config>(defaultConfig);
|
|
31
|
+
const { moneyNumberAttrs, symbolAttrs } = useUI<Config>(defaultConfig);
|
|
66
32
|
</script>
|
|
67
33
|
|
|
68
34
|
<template>
|
|
69
|
-
<
|
|
70
|
-
|
|
35
|
+
<UNumber
|
|
36
|
+
:value="value"
|
|
37
|
+
:size="size"
|
|
38
|
+
:color="color"
|
|
39
|
+
:sign="sign"
|
|
40
|
+
:align="align"
|
|
41
|
+
:min-fraction-digits="minFractionDigits"
|
|
42
|
+
:max-fraction-digits="maxFractionDigits"
|
|
43
|
+
:decimal-separator="decimalSeparator"
|
|
44
|
+
:thousands-separator="thousandsSeparator"
|
|
45
|
+
v-bind="moneyNumberAttrs"
|
|
46
|
+
:data-test="dataTest"
|
|
47
|
+
>
|
|
48
|
+
<template #left>
|
|
71
49
|
<!-- @slot Use it to add something before money amount. -->
|
|
72
50
|
<slot name="left" />
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
<div v-bind="sumAttrs" :data-test="dataTest">
|
|
76
|
-
<span
|
|
77
|
-
v-if="currencySymbolPosition.left && symbol"
|
|
78
|
-
v-bind="symbolAttrs"
|
|
79
|
-
v-text="symbol + currencySpace"
|
|
80
|
-
/>
|
|
81
|
-
|
|
82
|
-
<span v-if="value" v-bind="mathSignAttrs" v-text="mathSign" />
|
|
83
|
-
|
|
84
|
-
<span v-bind="integerAttrs" v-text="preparedMoney.integer" />
|
|
85
|
-
|
|
86
|
-
<span
|
|
87
|
-
v-if="maxFractionDigits > 0"
|
|
88
|
-
v-bind="pennyAttrs"
|
|
89
|
-
v-text="preparedMoney.decimalSeparator + preparedMoney.penny"
|
|
90
|
-
/>
|
|
91
|
-
|
|
92
|
-
<span
|
|
93
|
-
v-if="currencySymbolPosition.right && symbol"
|
|
94
|
-
v-bind="symbolAttrs"
|
|
95
|
-
v-text="currencySpace + symbol"
|
|
96
|
-
/>
|
|
97
|
-
</div>
|
|
51
|
+
<span v-if="currencySymbolPosition.left && symbol" v-bind="symbolAttrs" v-text="symbol" />
|
|
52
|
+
</template>
|
|
98
53
|
|
|
99
|
-
<
|
|
54
|
+
<template #right>
|
|
55
|
+
<span v-if="currencySymbolPosition.right && symbol" v-bind="symbolAttrs" v-text="symbol" />
|
|
100
56
|
<!-- @slot Use it to add something after money amount. -->
|
|
101
57
|
<slot name="right" />
|
|
102
|
-
</
|
|
103
|
-
</
|
|
58
|
+
</template>
|
|
59
|
+
</UNumber>
|
|
104
60
|
</template>
|
package/ui.text-money/config.ts
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
export default /*tw*/ {
|
|
2
|
-
|
|
3
|
-
base: "
|
|
2
|
+
moneyNumber: {
|
|
3
|
+
base: "{UNumber}",
|
|
4
4
|
variants: {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
grayscale: "text-gray-900",
|
|
8
|
-
},
|
|
9
|
-
align: {
|
|
10
|
-
left: "justify-start",
|
|
11
|
-
right: "justify-end",
|
|
5
|
+
planned: {
|
|
6
|
+
true: "opacity-75 before:content-['('] after:content-[')']",
|
|
12
7
|
},
|
|
13
8
|
},
|
|
14
9
|
},
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
sum: {
|
|
10
|
+
symbol: {
|
|
11
|
+
base: "text-{color}-600 mx-1",
|
|
18
12
|
variants: {
|
|
19
13
|
size: {
|
|
20
14
|
xs: "text-xs",
|
|
@@ -26,27 +20,13 @@ export default /*tw*/ {
|
|
|
26
20
|
"3xl": "text-3xl",
|
|
27
21
|
"4xl": "text-4xl",
|
|
28
22
|
},
|
|
29
|
-
planned: {
|
|
30
|
-
true: "opacity-75 before:content-['('] after:content-[')']",
|
|
31
|
-
},
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
symbol: "",
|
|
35
|
-
mathSign: "",
|
|
36
|
-
integer: "",
|
|
37
|
-
penny: {
|
|
38
|
-
variants: {
|
|
39
|
-
size: {
|
|
40
|
-
xs: "text-2xs",
|
|
41
|
-
sm: "text-xs",
|
|
42
|
-
md: "text-sm",
|
|
43
|
-
lg: "text-base",
|
|
44
|
-
xl: "text-lg",
|
|
45
|
-
"2xl": "text-xl",
|
|
46
|
-
"3xl": "text-2xl",
|
|
47
|
-
"4xl": "text-3xl",
|
|
48
|
-
},
|
|
49
23
|
},
|
|
24
|
+
compoundVariants: [
|
|
25
|
+
{ symbolDivided: false, symbolAlign: "left", class: "mr-0" },
|
|
26
|
+
{ symbolDivided: false, symbolAlign: "right", class: "ml-0" },
|
|
27
|
+
{ planned: true, symbolAlign: "left", class: "ml-0" },
|
|
28
|
+
{ planned: true, symbolAlign: "right", class: "mr-0" },
|
|
29
|
+
],
|
|
50
30
|
},
|
|
51
31
|
defaults: {
|
|
52
32
|
color: "grayscale",
|
|
@@ -8,6 +8,9 @@ import {
|
|
|
8
8
|
import UMoney from "../../ui.text-money/UMoney.vue";
|
|
9
9
|
import UIcon from "../../ui.image-icon/UIcon.vue";
|
|
10
10
|
import URow from "../../ui.container-row/URow.vue";
|
|
11
|
+
import UText from "../../ui.text-block/UText.vue";
|
|
12
|
+
import UDivider from "../../ui.container-divider/UDivider.vue";
|
|
13
|
+
|
|
11
14
|
import DebitIcon from "../../ui.text-money/assets/debit.svg?component";
|
|
12
15
|
import CreditIcon from "../../ui.text-money/assets/credit.svg?component";
|
|
13
16
|
|
|
@@ -16,17 +19,15 @@ import type { Props } from "../types.ts";
|
|
|
16
19
|
|
|
17
20
|
interface UMoneyArgs extends Props {
|
|
18
21
|
slotTemplate?: string;
|
|
19
|
-
enum: "color" | "size" | "sign" | "symbolAlign"
|
|
22
|
+
enum: "color" | "size" | "sign" | "symbolAlign";
|
|
20
23
|
}
|
|
21
24
|
|
|
22
|
-
const COMPONENT_CLASSES = "flex justify-center w-1/6";
|
|
23
|
-
|
|
24
25
|
export default {
|
|
25
|
-
id: "
|
|
26
|
+
id: "4060",
|
|
26
27
|
title: "Text & Content / Money",
|
|
27
28
|
component: UMoney,
|
|
28
29
|
args: {
|
|
29
|
-
value:
|
|
30
|
+
value: -14.24,
|
|
30
31
|
symbol: "$",
|
|
31
32
|
sign: "auto",
|
|
32
33
|
},
|
|
@@ -45,24 +46,17 @@ const DefaultTemplate: StoryFn<UMoneyArgs> = (args: UMoneyArgs) => ({
|
|
|
45
46
|
setup() {
|
|
46
47
|
const slots = getSlotNames(UMoney.__name);
|
|
47
48
|
|
|
48
|
-
|
|
49
|
-
Debit: DebitIcon,
|
|
50
|
-
Credit: CreditIcon,
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
return { args, slots, icons };
|
|
49
|
+
return { args, slots };
|
|
54
50
|
},
|
|
55
51
|
template: `
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
</UMoney>
|
|
60
|
-
</div>
|
|
52
|
+
<UMoney v-bind="args">
|
|
53
|
+
${args.slotTemplate || getSlotsFragment("")}
|
|
54
|
+
</UMoney>
|
|
61
55
|
`,
|
|
62
56
|
});
|
|
63
57
|
|
|
64
58
|
const EnumVariantTemplate: StoryFn<UMoneyArgs> = (args: UMoneyArgs, { argTypes }) => ({
|
|
65
|
-
components: { UMoney, URow },
|
|
59
|
+
components: { UMoney, URow, UText, UDivider },
|
|
66
60
|
setup() {
|
|
67
61
|
const slots = getSlotNames(UMoney.__name);
|
|
68
62
|
|
|
@@ -74,29 +68,26 @@ const EnumVariantTemplate: StoryFn<UMoneyArgs> = (args: UMoneyArgs, { argTypes }
|
|
|
74
68
|
},
|
|
75
69
|
template: `
|
|
76
70
|
<URow>
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
71
|
+
<UMoney
|
|
72
|
+
v-for="(option, index) in options"
|
|
73
|
+
:key="index"
|
|
74
|
+
v-bind="args"
|
|
75
|
+
:[args.enum]="option"
|
|
76
|
+
>
|
|
77
|
+
<template #right v-if="args.enum === 'sign'">
|
|
78
|
+
<UText size="lg" class="ml-1">{{ option }}</UText>
|
|
79
|
+
<UDivider vertical variant="dark" class="h-5" />
|
|
80
|
+
</template>
|
|
81
|
+
</UMoney>
|
|
80
82
|
</URow>
|
|
81
83
|
`,
|
|
82
|
-
created() {
|
|
83
|
-
this.mxArgTypes = argTypes;
|
|
84
|
-
},
|
|
85
84
|
});
|
|
86
85
|
|
|
87
86
|
export const Default = DefaultTemplate.bind({});
|
|
88
87
|
Default.args = {};
|
|
89
88
|
|
|
90
|
-
export const OtherValues = DefaultTemplate.bind({});
|
|
91
|
-
OtherValues.args = { value: 10, symbol: "$", sign: "negative" };
|
|
92
|
-
|
|
93
89
|
export const Colors = EnumVariantTemplate.bind({});
|
|
94
|
-
Colors.args = {
|
|
95
|
-
enum: "color",
|
|
96
|
-
value: 0,
|
|
97
|
-
symbol: "$",
|
|
98
|
-
sign: "auto",
|
|
99
|
-
};
|
|
90
|
+
Colors.args = { enum: "color" };
|
|
100
91
|
|
|
101
92
|
export const Sizes = EnumVariantTemplate.bind({});
|
|
102
93
|
Sizes.args = { enum: "size" };
|
|
@@ -110,26 +101,32 @@ SymbolAlign.args = { enum: "symbolAlign" };
|
|
|
110
101
|
export const Planned = DefaultTemplate.bind({});
|
|
111
102
|
Planned.args = { planned: true };
|
|
112
103
|
|
|
113
|
-
export const
|
|
114
|
-
|
|
104
|
+
export const MinFractionDigits4 = DefaultTemplate.bind({});
|
|
105
|
+
MinFractionDigits4.args = { minFractionDigits: 4, maxFractionDigits: 4 };
|
|
106
|
+
|
|
107
|
+
export const Slots: StoryFn<UMoneyArgs> = (args) => ({
|
|
108
|
+
components: { UMoney, UIcon, URow },
|
|
109
|
+
setup() {
|
|
110
|
+
const icons = {
|
|
111
|
+
Debit: DebitIcon,
|
|
112
|
+
Credit: CreditIcon,
|
|
113
|
+
};
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
return { args, icons };
|
|
116
|
+
},
|
|
117
|
+
template: `
|
|
118
|
+
<URow>
|
|
119
|
+
<UMoney v-bind="args">
|
|
120
|
+
<template #left>
|
|
121
|
+
<UIcon :src="icons.Debit" size="3xs" />
|
|
122
|
+
</template>
|
|
123
|
+
</UMoney>
|
|
118
124
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
</
|
|
125
|
-
`,
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
export const SlotRight = DefaultTemplate.bind({});
|
|
129
|
-
SlotRight.args = {
|
|
130
|
-
slotTemplate: `
|
|
131
|
-
<template #right>
|
|
132
|
-
<UIcon :src="icons.Credit" size="3xs" />
|
|
133
|
-
</template>
|
|
125
|
+
<UMoney v-bind="args">
|
|
126
|
+
<template #right>
|
|
127
|
+
<UIcon :src="icons.Credit" size="3xs" />
|
|
128
|
+
</template>
|
|
129
|
+
</UMoney>
|
|
130
|
+
</URow>
|
|
134
131
|
`,
|
|
135
|
-
};
|
|
132
|
+
});
|
package/ui.text-money/types.ts
CHANGED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from "vue";
|
|
3
|
+
|
|
4
|
+
import useUI from "../composables/useUI.ts";
|
|
5
|
+
import { getDefaults } from "../utils/ui.ts";
|
|
6
|
+
|
|
7
|
+
import { COMPONENT_NAME } from "./constants.ts";
|
|
8
|
+
import defaultConfig from "./config.ts";
|
|
9
|
+
import { separatedNumber, MATH_SIGN_TYPE, MATH_SIGN } from "./utilNumber.ts";
|
|
10
|
+
|
|
11
|
+
import type { Props, Config } from "./types.ts";
|
|
12
|
+
|
|
13
|
+
defineOptions({ inheritAttrs: false });
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(defineProps<Props>(), {
|
|
16
|
+
...getDefaults<Props, Config>(defaultConfig, COMPONENT_NAME),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const numericValue = computed(() => {
|
|
20
|
+
if (typeof props.value === "string") {
|
|
21
|
+
return parseFloat(props.value);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return props.value;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const mathSign = computed(() => {
|
|
28
|
+
let type = "";
|
|
29
|
+
|
|
30
|
+
if (props.sign === MATH_SIGN_TYPE.unsigned) type = "";
|
|
31
|
+
if (props.sign === MATH_SIGN_TYPE.positive) type = MATH_SIGN.PLUS;
|
|
32
|
+
if (props.sign === MATH_SIGN_TYPE.negative) type = MATH_SIGN.MINUS;
|
|
33
|
+
if (props.sign === MATH_SIGN_TYPE.auto && numericValue.value < 0) type = MATH_SIGN.MINUS;
|
|
34
|
+
|
|
35
|
+
return type;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const preparedNumber = computed(() => {
|
|
39
|
+
return separatedNumber(
|
|
40
|
+
Math.abs(numericValue.value || 0),
|
|
41
|
+
props.minFractionDigits,
|
|
42
|
+
props.maxFractionDigits,
|
|
43
|
+
props.decimalSeparator,
|
|
44
|
+
props.thousandsSeparator,
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Get element / nested component attributes for each config token ✨
|
|
50
|
+
* Applies: `class`, `config`, redefined default `props` and dev `vl-...` attributes.
|
|
51
|
+
*/
|
|
52
|
+
const { numberAttrs, sumAttrs, mathSignAttrs, integerAttrs, decimalAttrs } =
|
|
53
|
+
useUI<Config>(defaultConfig);
|
|
54
|
+
</script>
|
|
55
|
+
|
|
56
|
+
<template>
|
|
57
|
+
<div v-bind="numberAttrs">
|
|
58
|
+
<!-- @slot Use it to add something before the number. -->
|
|
59
|
+
<slot name="left" />
|
|
60
|
+
|
|
61
|
+
<div v-bind="sumAttrs" :data-test="dataTest">
|
|
62
|
+
<span v-if="value" v-bind="mathSignAttrs" v-text="mathSign" />
|
|
63
|
+
|
|
64
|
+
<span v-bind="integerAttrs" v-text="preparedNumber.integer" />
|
|
65
|
+
|
|
66
|
+
<span
|
|
67
|
+
v-if="maxFractionDigits > 0"
|
|
68
|
+
v-bind="decimalAttrs"
|
|
69
|
+
v-text="preparedNumber.decimalSeparator + preparedNumber.decimal"
|
|
70
|
+
/>
|
|
71
|
+
</div>
|
|
72
|
+
|
|
73
|
+
<!-- @slot Use it to add something after the number. -->
|
|
74
|
+
<slot name="right" />
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export default /*tw*/ {
|
|
2
|
+
number: {
|
|
3
|
+
base: "whitespace-nowrap flex items-center text-{color}-600",
|
|
4
|
+
variants: {
|
|
5
|
+
color: {
|
|
6
|
+
white: "text-white",
|
|
7
|
+
grayscale: "text-gray-900",
|
|
8
|
+
},
|
|
9
|
+
align: {
|
|
10
|
+
left: "justify-start",
|
|
11
|
+
right: "justify-end",
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
sum: {
|
|
16
|
+
variants: {
|
|
17
|
+
size: {
|
|
18
|
+
xs: "text-xs",
|
|
19
|
+
sm: "text-sm",
|
|
20
|
+
md: "text-base",
|
|
21
|
+
lg: "text-lg",
|
|
22
|
+
xl: "text-xl",
|
|
23
|
+
"2xl": "text-2xl",
|
|
24
|
+
"3xl": "text-3xl",
|
|
25
|
+
"4xl": "text-4xl",
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
mathSign: "",
|
|
30
|
+
integer: "",
|
|
31
|
+
decimal: "",
|
|
32
|
+
defaults: {
|
|
33
|
+
color: "grayscale",
|
|
34
|
+
size: "md",
|
|
35
|
+
sign: "auto",
|
|
36
|
+
align: "left",
|
|
37
|
+
minFractionDigits: 0,
|
|
38
|
+
maxFractionDigits: 2,
|
|
39
|
+
decimalSeparator: ",",
|
|
40
|
+
thousandsSeparator: " ",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Meta, Title, Subtitle, Description, Primary, Controls, Stories, Source } from "@storybook/blocks";
|
|
2
|
+
import { getSource } from "../../utils/storybook.ts";
|
|
3
|
+
|
|
4
|
+
import * as stories from "./stories.ts";
|
|
5
|
+
import defaultConfig from "../config.ts?raw"
|
|
6
|
+
|
|
7
|
+
<Meta of={stories} />
|
|
8
|
+
<Title of={stories} />
|
|
9
|
+
<Subtitle of={stories} />
|
|
10
|
+
<Description of={stories} />
|
|
11
|
+
<Primary of={stories} />
|
|
12
|
+
<Controls of={stories.Default} />
|
|
13
|
+
<Stories of={stories} />
|
|
14
|
+
|
|
15
|
+
## Default config
|
|
16
|
+
<Source code={getSource(defaultConfig)} language="jsx" dark />
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { getArgTypes, getSlotNames, getSlotsFragment } from "../../utils/storybook.ts";
|
|
2
|
+
|
|
3
|
+
import UNumber from "../../ui.text-number/UNumber.vue";
|
|
4
|
+
import UIcon from "../../ui.image-icon/UIcon.vue";
|
|
5
|
+
import URow from "../../ui.container-row/URow.vue";
|
|
6
|
+
import UText from "../../ui.text-block/UText.vue";
|
|
7
|
+
import UBadge from "../../ui.text-badge/UBadge.vue";
|
|
8
|
+
import UDivider from "../../ui.container-divider/UDivider.vue";
|
|
9
|
+
|
|
10
|
+
import type { Meta, StoryFn } from "@storybook/vue3";
|
|
11
|
+
import type { Props } from "../types.ts";
|
|
12
|
+
|
|
13
|
+
interface UNumberArgs extends Props {
|
|
14
|
+
slotTemplate?: string;
|
|
15
|
+
enum: "color" | "size" | "sign";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
id: "4050",
|
|
20
|
+
title: "Text & Content / Number",
|
|
21
|
+
component: UNumber,
|
|
22
|
+
args: {
|
|
23
|
+
value: -14.24,
|
|
24
|
+
sign: "auto",
|
|
25
|
+
},
|
|
26
|
+
argTypes: {
|
|
27
|
+
...getArgTypes(UNumber.__name),
|
|
28
|
+
},
|
|
29
|
+
} as Meta;
|
|
30
|
+
|
|
31
|
+
const DefaultTemplate: StoryFn<UNumberArgs> = (args: UNumberArgs) => ({
|
|
32
|
+
components: { UNumber, UIcon, UBadge },
|
|
33
|
+
setup() {
|
|
34
|
+
const slots = getSlotNames(UNumber.__name);
|
|
35
|
+
|
|
36
|
+
return { args, slots };
|
|
37
|
+
},
|
|
38
|
+
template: `
|
|
39
|
+
<UNumber v-bind="args">
|
|
40
|
+
${args.slotTemplate || getSlotsFragment("")}
|
|
41
|
+
</UNumber>
|
|
42
|
+
`,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const EnumVariantTemplate: StoryFn<UNumberArgs> = (args: UNumberArgs, { argTypes }) => ({
|
|
46
|
+
components: { UNumber, URow, UText, UDivider },
|
|
47
|
+
setup() {
|
|
48
|
+
return { args, options: argTypes?.[args.enum]?.options };
|
|
49
|
+
},
|
|
50
|
+
template: `
|
|
51
|
+
<URow>
|
|
52
|
+
<UNumber
|
|
53
|
+
v-for="(option, index) in options"
|
|
54
|
+
:key="index"
|
|
55
|
+
v-bind="args"
|
|
56
|
+
:[args.enum]="option"
|
|
57
|
+
>
|
|
58
|
+
<template #right v-if="args.enum === 'sign'">
|
|
59
|
+
<UText size="lg" class="ml-1">{{ option }}</UText>
|
|
60
|
+
<UDivider vertical variant="dark" class="h-5" />
|
|
61
|
+
</template>
|
|
62
|
+
</UNumber>
|
|
63
|
+
</URow>
|
|
64
|
+
`,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
export const Default = DefaultTemplate.bind({});
|
|
68
|
+
Default.args = {};
|
|
69
|
+
|
|
70
|
+
export const Colors = EnumVariantTemplate.bind({});
|
|
71
|
+
Colors.args = {
|
|
72
|
+
enum: "color",
|
|
73
|
+
sign: "auto",
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export const Sizes = EnumVariantTemplate.bind({});
|
|
77
|
+
Sizes.args = { enum: "size" };
|
|
78
|
+
|
|
79
|
+
export const Sign = EnumVariantTemplate.bind({});
|
|
80
|
+
Sign.args = { enum: "sign" };
|
|
81
|
+
|
|
82
|
+
export const MinFractionDigits4 = DefaultTemplate.bind({});
|
|
83
|
+
MinFractionDigits4.args = { minFractionDigits: 4, maxFractionDigits: 4 };
|
|
84
|
+
|
|
85
|
+
export const Slots: StoryFn<UNumberArgs> = (args) => ({
|
|
86
|
+
components: { UNumber, UIcon, URow, UBadge },
|
|
87
|
+
setup() {
|
|
88
|
+
return { args };
|
|
89
|
+
},
|
|
90
|
+
template: `
|
|
91
|
+
<URow>
|
|
92
|
+
<UNumber v-bind="args">
|
|
93
|
+
<template #left>
|
|
94
|
+
<UIcon name="confirmation_number" color="green" />
|
|
95
|
+
</template>
|
|
96
|
+
</UNumber>
|
|
97
|
+
|
|
98
|
+
<UNumber v-bind="args">
|
|
99
|
+
<template #right>
|
|
100
|
+
<UBadge label="Quantity" color="green" />
|
|
101
|
+
</template>
|
|
102
|
+
</UNumber>
|
|
103
|
+
</URow>
|
|
104
|
+
`,
|
|
105
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import defaultConfig from "./config.ts";
|
|
2
|
+
import type { ComponentConfig } from "../types.ts";
|
|
3
|
+
|
|
4
|
+
export type Config = typeof defaultConfig;
|
|
5
|
+
|
|
6
|
+
export interface Props {
|
|
7
|
+
/**
|
|
8
|
+
* Number value.
|
|
9
|
+
*/
|
|
10
|
+
value?: number | string;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Number size.
|
|
14
|
+
*/
|
|
15
|
+
size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Number color.
|
|
19
|
+
*/
|
|
20
|
+
color?:
|
|
21
|
+
| "grayscale"
|
|
22
|
+
| "red"
|
|
23
|
+
| "orange"
|
|
24
|
+
| "amber"
|
|
25
|
+
| "yellow"
|
|
26
|
+
| "lime"
|
|
27
|
+
| "green"
|
|
28
|
+
| "emerald"
|
|
29
|
+
| "teal"
|
|
30
|
+
| "cyan"
|
|
31
|
+
| "sky"
|
|
32
|
+
| "blue"
|
|
33
|
+
| "indigo"
|
|
34
|
+
| "violet"
|
|
35
|
+
| "purple"
|
|
36
|
+
| "fuchsia"
|
|
37
|
+
| "pink"
|
|
38
|
+
| "rose"
|
|
39
|
+
| "gray"
|
|
40
|
+
| "white"
|
|
41
|
+
| "brand";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Number sign.
|
|
45
|
+
*/
|
|
46
|
+
sign?: "auto" | "positive" | "negative" | "unsigned";
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Minimal number of signs after the decimal separator (fractional part of a number).
|
|
50
|
+
*/
|
|
51
|
+
minFractionDigits?: number;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Maximal number of signs after the decimal separator (fractional part of a number).
|
|
55
|
+
*/
|
|
56
|
+
maxFractionDigits?: number;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* A symbol used to separate the integer part from the fractional part of a number.
|
|
60
|
+
*/
|
|
61
|
+
decimalSeparator?: string;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* A symbol used to separate the thousand parts of a number.
|
|
65
|
+
*/
|
|
66
|
+
thousandsSeparator?: string;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Number align.
|
|
70
|
+
*/
|
|
71
|
+
align?: "right" | "left";
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Component config object.
|
|
75
|
+
*/
|
|
76
|
+
config?: ComponentConfig<Config>;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Data-test attribute for automated testing.
|
|
80
|
+
*/
|
|
81
|
+
dataTest?: string;
|
|
82
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export const
|
|
1
|
+
export const MATH_SIGN_TYPE = {
|
|
2
2
|
auto: "auto",
|
|
3
3
|
positive: "positive",
|
|
4
4
|
negative: "negative",
|
|
@@ -10,12 +10,11 @@ export const MATH_SIGN = {
|
|
|
10
10
|
MINUS: "-",
|
|
11
11
|
};
|
|
12
12
|
|
|
13
|
-
export const ADD_SPACE_IN_MONEY_REG_EX = /(\d)(?=(\d{3})+(\D|$))/g;
|
|
14
13
|
export const SINGLE_ZERO = "0";
|
|
15
14
|
export const DOUBLE_ZERO = "00";
|
|
16
15
|
|
|
17
|
-
export function
|
|
18
|
-
|
|
16
|
+
export function separatedNumber(
|
|
17
|
+
value: number,
|
|
19
18
|
minFractionDigits = 0,
|
|
20
19
|
maxFractionDigits = 2,
|
|
21
20
|
decimalSeparator = ",",
|
|
@@ -26,28 +25,28 @@ export function separatedMoney(
|
|
|
26
25
|
maximumFractionDigits: Number(maxFractionDigits),
|
|
27
26
|
};
|
|
28
27
|
|
|
29
|
-
const
|
|
30
|
-
|
|
28
|
+
const formattedNumber =
|
|
29
|
+
value !== undefined && value !== null ? Number(value).toLocaleString("en-US", options) : "0";
|
|
31
30
|
|
|
32
|
-
let [integer,
|
|
31
|
+
let [integer, decimal = ""] = formattedNumber.split(".");
|
|
33
32
|
|
|
34
33
|
integer = integer.replace(/,/g, thousandsSeparator);
|
|
35
34
|
|
|
36
|
-
if (!
|
|
35
|
+
if (!value && value !== 0) {
|
|
37
36
|
integer = SINGLE_ZERO;
|
|
38
|
-
|
|
37
|
+
decimal = DOUBLE_ZERO;
|
|
39
38
|
}
|
|
40
39
|
|
|
41
|
-
if (
|
|
40
|
+
if (decimal === "") {
|
|
42
41
|
decimalSeparator = "";
|
|
43
42
|
}
|
|
44
43
|
|
|
45
44
|
if (minFractionDigits === 0 && maxFractionDigits === 0) {
|
|
46
45
|
decimalSeparator = "";
|
|
47
|
-
|
|
48
|
-
} else if (
|
|
49
|
-
|
|
46
|
+
decimal = "";
|
|
47
|
+
} else if (decimal.length < minFractionDigits) {
|
|
48
|
+
decimal = decimal.padEnd(minFractionDigits, "0");
|
|
50
49
|
}
|
|
51
50
|
|
|
52
|
-
return { integer,
|
|
51
|
+
return { integer, decimal, decimalSeparator };
|
|
53
52
|
}
|