@flux-ui/components 3.0.0 → 3.1.2

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 (45) hide show
  1. package/dist/component/FluxBreadcrumb.vue.d.ts +15 -0
  2. package/dist/component/FluxBreadcrumbItem.vue.d.ts +23 -0
  3. package/dist/component/{FluxCheckbox.vue.d.ts → FluxFormCheckbox.vue.d.ts} +3 -0
  4. package/dist/component/FluxFormCheckboxGroup.vue.d.ts +26 -0
  5. package/dist/component/FluxFormField.vue.d.ts +1 -0
  6. package/dist/component/FluxFormNumberInput.vue.d.ts +26 -0
  7. package/dist/component/FluxFormRadio.vue.d.ts +18 -0
  8. package/dist/component/FluxFormRadioGroup.vue.d.ts +27 -0
  9. package/dist/component/FluxSkeleton.vue.d.ts +9 -0
  10. package/dist/component/index.d.ts +8 -1
  11. package/dist/composable/index.d.ts +2 -0
  12. package/dist/composable/useFormCheckboxGroupInjection.d.ts +2 -0
  13. package/dist/composable/useFormFieldInjection.d.ts +3 -2
  14. package/dist/composable/useFormRadioGroupInjection.d.ts +2 -0
  15. package/dist/data/di.d.ts +23 -0
  16. package/dist/index.css +1374 -1012
  17. package/dist/index.js +893 -399
  18. package/dist/index.js.map +1 -1
  19. package/package.json +9 -9
  20. package/src/component/FluxBreadcrumb.vue +24 -0
  21. package/src/component/FluxBreadcrumbItem.vue +72 -0
  22. package/src/component/FluxDataTable.vue +3 -3
  23. package/src/component/FluxFormCheckbox.vue +123 -0
  24. package/src/component/FluxFormCheckboxGroup.vue +57 -0
  25. package/src/component/FluxFormField.vue +23 -7
  26. package/src/component/FluxFormNumberInput.vue +180 -0
  27. package/src/component/FluxFormPinInput.vue +2 -2
  28. package/src/component/FluxFormRadio.vue +76 -0
  29. package/src/component/FluxFormRadioGroup.vue +53 -0
  30. package/src/component/FluxQuantitySelector.vue +4 -4
  31. package/src/component/FluxSkeleton.vue +46 -0
  32. package/src/component/FluxToggle.vue +4 -4
  33. package/src/component/index.ts +8 -1
  34. package/src/composable/index.ts +2 -0
  35. package/src/composable/useFormCheckboxGroupInjection.ts +6 -0
  36. package/src/composable/useFormFieldInjection.ts +7 -3
  37. package/src/composable/useFormRadioGroupInjection.ts +13 -0
  38. package/src/css/component/BorderBeam.module.scss +51 -22
  39. package/src/css/component/Breadcrumb.module.scss +86 -0
  40. package/src/css/component/Divider.module.scss +1 -0
  41. package/src/css/component/Form.module.scss +251 -45
  42. package/src/css/component/SegmentedControl.module.scss +18 -18
  43. package/src/css/component/Skeleton.module.scss +67 -0
  44. package/src/data/di.ts +30 -0
  45. package/src/component/FluxCheckbox.vue +0 -87
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@flux-ui/components",
3
3
  "description": "A set of opiniated UI components.",
4
- "version": "3.0.0",
4
+ "version": "3.1.2",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "funding": "https://github.com/sponsors/basmilius",
@@ -55,10 +55,10 @@
55
55
  "**/dist/index.css"
56
56
  ],
57
57
  "dependencies": {
58
- "@basmilius/common": "^3.40.0",
59
- "@basmilius/utils": "^3.40.0",
60
- "@flux-ui/internals": "3.0.0",
61
- "@flux-ui/types": "3.0.0",
58
+ "@basmilius/common": "^3.41.0",
59
+ "@basmilius/utils": "^3.41.0",
60
+ "@flux-ui/internals": "3.1.2",
61
+ "@flux-ui/types": "3.1.2",
62
62
  "@fortawesome/fontawesome-common-types": "^7.2.0",
63
63
  "clsx": "^2.1.1",
64
64
  "imask": "^7.6.1",
@@ -66,18 +66,18 @@
66
66
  },
67
67
  "peerDependencies": {
68
68
  "luxon": "^3.7.2",
69
- "vue": "^3.6.0-beta.13"
69
+ "vue": "^3.6.0-beta.15"
70
70
  },
71
71
  "devDependencies": {
72
- "@basmilius/vite-preset": "^3.40.0",
72
+ "@basmilius/vite-preset": "^3.41.0",
73
73
  "@types/lodash-es": "^4.17.12",
74
74
  "@types/luxon": "^3.7.1",
75
- "@types/node": "^25.9.1",
75
+ "@types/node": "^25.9.3",
76
76
  "@vitejs/plugin-vue": "^6.0.7",
77
77
  "@vue/tsconfig": "^0.9.1",
78
78
  "sass-embedded": "^1.100.0",
79
79
  "typescript": "^6.0.3",
80
80
  "vite": "^8.0.16",
81
- "vue-tsc": "^3.3.3"
81
+ "vue-tsc": "^3.3.5"
82
82
  }
83
83
  }
@@ -0,0 +1,24 @@
1
+ <template>
2
+ <nav
3
+ :class="$style.breadcrumb"
4
+ :aria-label="ariaLabel">
5
+ <ol :class="$style.breadcrumbList">
6
+ <slot/>
7
+ </ol>
8
+ </nav>
9
+ </template>
10
+
11
+ <script
12
+ lang="ts"
13
+ setup>
14
+ import type { VNode } from 'vue';
15
+ import $style from '~flux/components/css/component/Breadcrumb.module.scss';
16
+
17
+ const {ariaLabel = 'Breadcrumb'} = defineProps<{
18
+ readonly ariaLabel?: string;
19
+ }>();
20
+
21
+ defineSlots<{
22
+ default(): VNode[];
23
+ }>();
24
+ </script>
@@ -0,0 +1,72 @@
1
+ <template>
2
+ <li :class="$style.breadcrumbItem">
3
+ <FluxPressable
4
+ :class="clsx(
5
+ $style.breadcrumbLink,
6
+ isCurrent && $style.isCurrent
7
+ )"
8
+ :component-type="componentType"
9
+ :href="href"
10
+ :to="to"
11
+ :aria-current="isCurrent ? 'page' : undefined"
12
+ @click="emit('click', $event)">
13
+ <FluxIcon
14
+ v-if="icon"
15
+ :class="$style.breadcrumbIcon"
16
+ :name="icon"
17
+ :size="16"/>
18
+
19
+ <span
20
+ v-if="label || $slots.default"
21
+ :class="$style.breadcrumbLabel">
22
+ <slot>{{ label }}</slot>
23
+ </span>
24
+ </FluxPressable>
25
+
26
+ <FluxIcon
27
+ aria-hidden="true"
28
+ :class="$style.breadcrumbSeparator"
29
+ name="slash-forward"
30
+ :size="12"/>
31
+ </li>
32
+ </template>
33
+
34
+ <script
35
+ lang="ts"
36
+ setup>
37
+ import type { FluxIconName, FluxPressableType, FluxTo } from '@flux-ui/types';
38
+ import { clsx } from 'clsx';
39
+ import { computed, type VNode } from 'vue';
40
+ import FluxIcon from './FluxIcon.vue';
41
+ import FluxPressable from './FluxPressable.vue';
42
+ import $style from '~flux/components/css/component/Breadcrumb.module.scss';
43
+
44
+ const emit = defineEmits<{
45
+ click: [MouseEvent];
46
+ }>();
47
+
48
+ const {href, to} = defineProps<{
49
+ readonly href?: string;
50
+ readonly icon?: FluxIconName;
51
+ readonly label?: string;
52
+ readonly to?: FluxTo;
53
+ }>();
54
+
55
+ defineSlots<{
56
+ default(): VNode[];
57
+ }>();
58
+
59
+ const componentType = computed<FluxPressableType>(() => {
60
+ if (to) {
61
+ return 'route';
62
+ }
63
+
64
+ if (href) {
65
+ return 'link';
66
+ }
67
+
68
+ return 'none';
69
+ });
70
+
71
+ const isCurrent = computed(() => componentType.value === 'none');
72
+ </script>
@@ -25,7 +25,7 @@
25
25
  v-if="selectionMode"
26
26
  is-shrinking
27
27
  :class="$style.tableCellSelection">
28
- <FluxCheckbox
28
+ <FluxFormCheckbox
29
29
  v-if="selectionMode === 'multiple'"
30
30
  :model-value="selectAllState"
31
31
  @update:model-value="onSelectAll"/>
@@ -72,7 +72,7 @@
72
72
  <FluxTableCell
73
73
  v-if="selectionMode"
74
74
  :class="$style.tableCellSelection">
75
- <FluxCheckbox
75
+ <FluxFormCheckbox
76
76
  :model-value="isItemSelected(item)"
77
77
  @update:model-value="onSelectRow(item)"/>
78
78
  </FluxTableCell>
@@ -93,7 +93,7 @@
93
93
  generic="T extends Record<string, any>">
94
94
  import { computed, unref, useTemplateRef, type VNode, watch } from 'vue';
95
95
  import { useDisabledInjection } from '~flux/components/composable';
96
- import FluxCheckbox from './FluxCheckbox.vue';
96
+ import FluxFormCheckbox from './FluxFormCheckbox.vue';
97
97
  import FluxPaginationBar from './FluxPaginationBar.vue';
98
98
  import FluxTable from './FluxTable.vue';
99
99
  import FluxTableCell from './FluxTableCell.vue';
@@ -0,0 +1,123 @@
1
+ <template>
2
+ <label
3
+ :class="clsx(
4
+ $style.formCheckbox,
5
+ disabled && $style.isDisabled,
6
+ isReadonlyResolved && $style.isReadonly,
7
+ errorResolved && $style.isInvalid
8
+ )"
9
+ :for="id">
10
+ <input
11
+ ref="input"
12
+ type="checkbox"
13
+ :class="$style.formCheckboxNative"
14
+ :id="id"
15
+ :checked="isChecked"
16
+ :disabled="disabled"
17
+ :aria-disabled="disabled ? true : undefined"
18
+ :aria-readonly="isReadonlyResolved ? true : undefined"
19
+ :aria-invalid="errorResolved ? true : undefined"
20
+ @change="onChange"
21
+ @click="onClick"/>
22
+
23
+ <button
24
+ aria-hidden="true"
25
+ :class="$style.formCheckboxElement"
26
+ tabindex="-1">
27
+ <FluxIcon
28
+ v-if="isIndeterminate"
29
+ name="minus"
30
+ :size="12"/>
31
+
32
+ <FluxIcon
33
+ v-else
34
+ name="check"
35
+ :size="12"/>
36
+ </button>
37
+
38
+ <span
39
+ v-if="label || subLabel"
40
+ :class="$style.formCheckboxText">
41
+ <span
42
+ v-if="label"
43
+ :class="$style.formCheckboxLabel">
44
+ {{ label }}
45
+ </span>
46
+
47
+ <span
48
+ v-if="subLabel"
49
+ :class="$style.formCheckboxSubLabel">
50
+ {{ subLabel }}
51
+ </span>
52
+ </span>
53
+ </label>
54
+ </template>
55
+
56
+ <script
57
+ lang="ts"
58
+ setup>
59
+ import type { FluxFormInputBaseProps } from '@flux-ui/types';
60
+ import { clsx } from 'clsx';
61
+ import { computed, toRef, unref, useTemplateRef, watchEffect } from 'vue';
62
+ import { useDisabled, useFormCheckboxGroupInjection, useFormFieldInjection } from '~flux/components/composable';
63
+ import type { FluxFormCheckboxGroupValue } from '~flux/components/data';
64
+ import FluxIcon from './FluxIcon.vue';
65
+ import $style from '~flux/components/css/component/Form.module.scss';
66
+
67
+ const modelValue = defineModel<boolean | null>({
68
+ default: false
69
+ });
70
+
71
+ const {
72
+ disabled: componentDisabled,
73
+ error,
74
+ isReadonly,
75
+ value
76
+ } = defineProps<Pick<FluxFormInputBaseProps, 'disabled' | 'error' | 'isReadonly'> & {
77
+ readonly label?: string;
78
+ readonly subLabel?: string;
79
+ readonly value?: FluxFormCheckboxGroupValue;
80
+ }>();
81
+
82
+ const group = useFormCheckboxGroupInjection();
83
+ const inputRef = useTemplateRef('input');
84
+ const {id} = useFormFieldInjection();
85
+
86
+ const isGrouped = computed(() => group !== null && value !== undefined);
87
+
88
+ const disabled = useDisabled(toRef(() => componentDisabled || (group?.disabled.value ?? false)));
89
+ const isReadonlyResolved = computed(() => isReadonly || (group?.isReadonly.value ?? false));
90
+ const errorResolved = computed(() => unref(isGrouped) ? group?.error.value : error);
91
+
92
+ const isChecked = computed(() => unref(isGrouped) ? group!.has(value!) : unref(modelValue) === true);
93
+ const isIndeterminate = computed(() => !unref(isGrouped) && unref(modelValue) === null);
94
+
95
+ function onChange(): void {
96
+ if (unref(isReadonlyResolved) || unref(disabled)) {
97
+ return;
98
+ }
99
+
100
+ if (unref(isGrouped)) {
101
+ group!.toggle(value!);
102
+ return;
103
+ }
104
+
105
+ modelValue.value = unref(isIndeterminate) ? true : !unref(modelValue);
106
+ }
107
+
108
+ function onClick(evt: MouseEvent): void {
109
+ if (unref(isReadonlyResolved)) {
110
+ evt.preventDefault();
111
+ }
112
+ }
113
+
114
+ watchEffect(() => {
115
+ const input = unref(inputRef);
116
+
117
+ if (!input) {
118
+ return;
119
+ }
120
+
121
+ input.indeterminate = unref(isIndeterminate);
122
+ });
123
+ </script>
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <div
3
+ :class="clsx(
4
+ $style.formCheckboxGroup,
5
+ isInline && $style.isInline
6
+ )"
7
+ :role="field?.isGroup ? undefined : 'group'"
8
+ :aria-label="field?.isGroup ? undefined : ariaLabel">
9
+ <slot/>
10
+ </div>
11
+ </template>
12
+
13
+ <script
14
+ lang="ts"
15
+ setup>
16
+ import type { FluxFormInputBaseProps } from '@flux-ui/types';
17
+ import { clsx } from 'clsx';
18
+ import { inject, provide, toRef, unref, type VNode } from 'vue';
19
+ import { useDisabled } from '~flux/components/composable';
20
+ import { FluxFormCheckboxGroupInjectionKey, FluxFormFieldInjectionKey, type FluxFormCheckboxGroupValue } from '~flux/components/data';
21
+ import $style from '~flux/components/css/component/Form.module.scss';
22
+
23
+ const modelValue = defineModel<FluxFormCheckboxGroupValue[]>({
24
+ default: () => []
25
+ });
26
+
27
+ const {
28
+ disabled: componentDisabled,
29
+ error,
30
+ isReadonly
31
+ } = defineProps<Pick<FluxFormInputBaseProps, 'disabled' | 'error' | 'isReadonly'> & {
32
+ readonly ariaLabel?: string;
33
+ readonly isInline?: boolean;
34
+ }>();
35
+
36
+ defineSlots<{
37
+ default(): VNode[];
38
+ }>();
39
+
40
+ const field = inject(FluxFormFieldInjectionKey, null);
41
+ const disabled = useDisabled(toRef(() => componentDisabled));
42
+
43
+ provide(FluxFormCheckboxGroupInjectionKey, {
44
+ modelValue,
45
+ disabled,
46
+ isReadonly: toRef(() => isReadonly ?? false),
47
+ error: toRef(() => error),
48
+ has: value => unref(modelValue).includes(value),
49
+ toggle(value) {
50
+ const current = unref(modelValue);
51
+
52
+ modelValue.value = current.includes(value)
53
+ ? current.filter(item => item !== value)
54
+ : [...current, value];
55
+ }
56
+ });
57
+ </script>
@@ -1,10 +1,15 @@
1
1
  <template>
2
- <div :class="$style.formField">
3
- <label
4
- :for="id"
2
+ <div
3
+ :class="$style.formField"
4
+ :role="isGroup ? 'group' : undefined"
5
+ :aria-labelledby="isGroup && label ? labelId : undefined">
6
+ <component
7
+ :is="isGroup ? 'div' : 'label'"
8
+ :for="isGroup ? undefined : id"
5
9
  :class="$style.formFieldHeader">
6
10
  <span
7
11
  v-if="label"
12
+ :id="isGroup ? labelId : undefined"
8
13
  :class="$style.formFieldLabel">
9
14
  {{ label }}
10
15
  </span>
@@ -22,7 +27,7 @@
22
27
  name="value"
23
28
  v-bind="{currentLength, error, hint, id, isOptional, label, maxLength}"/>
24
29
  </span>
25
- </label>
30
+ </component>
26
31
 
27
32
  <slot v-bind="{id}"/>
28
33
 
@@ -52,13 +57,16 @@
52
57
  <script
53
58
  lang="ts"
54
59
  setup>
55
- import { provide, useId, type VNode } from 'vue';
60
+ import { computed, provide, useId, type VNode } from 'vue';
56
61
  import { useTranslate } from '~flux/components/composable/private';
57
62
  import { FluxFormFieldInjectionKey } from '~flux/components/data';
58
63
  import FluxFormFieldAddition from './FluxFormFieldAddition.vue';
59
64
  import $style from '~flux/components/css/component/Form.module.scss';
60
65
 
61
- defineProps<{
66
+ const {
67
+ as = 'field'
68
+ } = defineProps<{
69
+ readonly as?: 'field' | 'group';
62
70
  readonly currentLength?: number;
63
71
  readonly error?: string;
64
72
  readonly hint?: string;
@@ -92,9 +100,17 @@
92
100
  }>();
93
101
 
94
102
  const id = useId();
103
+ const labelId = useId();
95
104
  const translate = useTranslate();
96
105
 
106
+ const isGroup = computed(() => as === 'group');
107
+
108
+ let controlCount = 0;
109
+
97
110
  provide(FluxFormFieldInjectionKey, {
98
- id
111
+ id,
112
+ labelId,
113
+ isGroup: as === 'group',
114
+ registerControl: () => controlCount++ === 0 ? id : `${id}-${controlCount - 1}`
99
115
  });
100
116
  </script>
@@ -0,0 +1,180 @@
1
+ <template>
2
+ <div
3
+ :class="clsx(
4
+ disabled ? $style.formInputDisabled : $style.formInputEnabled,
5
+ isCondensed && $style.isCondensed,
6
+ isSecondary && $style.isSecondary,
7
+ error && $style.isInvalid
8
+ )"
9
+ :aria-disabled="disabled ? true : undefined">
10
+ <input
11
+ ref="input"
12
+ :class="clsx($style.formInputNative, $style.formNumberInputNative)"
13
+ :id="id"
14
+ :name="name"
15
+ :autofocus="autoFocus"
16
+ inputmode="decimal"
17
+ type="number"
18
+ :aria-disabled="disabled ? true : undefined"
19
+ :aria-invalid="error ? true : undefined"
20
+ :disabled="disabled"
21
+ :max="max"
22
+ :min="min"
23
+ :placeholder="placeholder"
24
+ :readonly="isReadonly"
25
+ :step="step"
26
+ :value="modelValue ?? ''"
27
+ @blur="onBlur"
28
+ @focus="onFocus"
29
+ @input="onInput"
30
+ @keydown="onKeyDown">
31
+
32
+ <span
33
+ aria-hidden="true"
34
+ :class="$style.formNumberInputButtons">
35
+ <button
36
+ type="button"
37
+ tabindex="-1"
38
+ :class="$style.formNumberInputButton"
39
+ :disabled="disabled || isReadonly || atMax"
40
+ @click="stepValue(1)">
41
+ <FluxIcon
42
+ name="chevron-up"
43
+ :size="12"/>
44
+ </button>
45
+
46
+ <button
47
+ type="button"
48
+ tabindex="-1"
49
+ :class="$style.formNumberInputButton"
50
+ :disabled="disabled || isReadonly || atMin"
51
+ @click="stepValue(-1)">
52
+ <FluxIcon
53
+ name="chevron-down"
54
+ :size="12"/>
55
+ </button>
56
+ </span>
57
+ </div>
58
+ </template>
59
+
60
+ <script
61
+ lang="ts"
62
+ setup>
63
+ import { unrefTemplateElement } from '@flux-ui/internals';
64
+ import type { FluxFormInputBaseProps } from '@flux-ui/types';
65
+ import { clsx } from 'clsx';
66
+ import { computed, toRef, unref, useTemplateRef } from 'vue';
67
+ import { useDisabled, useFormFieldInjection } from '~flux/components/composable';
68
+ import FluxIcon from './FluxIcon.vue';
69
+ import $style from '~flux/components/css/component/Form.module.scss';
70
+
71
+ const emit = defineEmits<{
72
+ blur: [];
73
+ focus: [];
74
+ }>();
75
+
76
+ const modelValue = defineModel<number | null>({
77
+ default: null
78
+ });
79
+
80
+ const {
81
+ disabled: componentDisabled,
82
+ isReadonly,
83
+ max,
84
+ min,
85
+ step = 1
86
+ } = defineProps<FluxFormInputBaseProps & {
87
+ readonly max?: number;
88
+ readonly min?: number;
89
+ readonly step?: number;
90
+ }>();
91
+
92
+ const disabled = useDisabled(toRef(() => componentDisabled));
93
+ const inputRef = useTemplateRef<HTMLInputElement>('input');
94
+ const {id} = useFormFieldInjection();
95
+
96
+ const atMax = computed(() => max !== undefined && modelValue.value !== null && modelValue.value >= max);
97
+ const atMin = computed(() => min !== undefined && modelValue.value !== null && modelValue.value <= min);
98
+
99
+ function blur(): void {
100
+ unrefTemplateElement(inputRef)?.blur();
101
+ }
102
+
103
+ function focus(): void {
104
+ unrefTemplateElement(inputRef)?.focus();
105
+ }
106
+
107
+ function clamp(value: number): number {
108
+ let result = value;
109
+
110
+ if (min !== undefined) {
111
+ result = Math.max(min, result);
112
+ }
113
+
114
+ if (max !== undefined) {
115
+ result = Math.min(max, result);
116
+ }
117
+
118
+ return result;
119
+ }
120
+
121
+ function round(value: number): number {
122
+ const decimals = (step.toString().split('.')[1] ?? '').length;
123
+
124
+ return decimals > 0 ? Number(value.toFixed(decimals)) : value;
125
+ }
126
+
127
+ function stepValue(direction: 1 | -1): void {
128
+ if (unref(disabled) || isReadonly) {
129
+ return;
130
+ }
131
+
132
+ const base = modelValue.value ?? min ?? 0;
133
+
134
+ modelValue.value = clamp(round(base + direction * step));
135
+ }
136
+
137
+ function onBlur(): void {
138
+ if (modelValue.value !== null) {
139
+ modelValue.value = clamp(modelValue.value);
140
+ }
141
+
142
+ emit('blur');
143
+ }
144
+
145
+ function onFocus(): void {
146
+ emit('focus');
147
+ }
148
+
149
+ function onInput(evt: Event): void {
150
+ const value = (evt.target as HTMLInputElement).value;
151
+
152
+ if (value === '') {
153
+ modelValue.value = null;
154
+ return;
155
+ }
156
+
157
+ const numeric = Number(value);
158
+
159
+ if (Number.isNaN(numeric)) {
160
+ return;
161
+ }
162
+
163
+ modelValue.value = numeric;
164
+ }
165
+
166
+ function onKeyDown(evt: KeyboardEvent): void {
167
+ if (evt.key === 'ArrowUp') {
168
+ stepValue(1);
169
+ evt.preventDefault();
170
+ } else if (evt.key === 'ArrowDown') {
171
+ stepValue(-1);
172
+ evt.preventDefault();
173
+ }
174
+ }
175
+
176
+ defineExpose({
177
+ blur,
178
+ focus
179
+ });
180
+ </script>
@@ -1,7 +1,7 @@
1
1
  <template>
2
2
  <fieldset
3
3
  :class="clsx(
4
- disabled ? $style.pinInputDisabled : $style.pinInputEnabled,
4
+ disabled ? $style.formPinInputDisabled : $style.formPinInputEnabled,
5
5
  error && $style.isInvalid
6
6
  )"
7
7
  :id="id"
@@ -16,7 +16,7 @@
16
16
  v-for="field of maxLength"
17
17
  :key="field"
18
18
  ref="fields"
19
- :class="$style.pinInputField"
19
+ :class="$style.formPinInputField"
20
20
  maxlength="1"
21
21
  :aria-label="translate('flux.pinDigit', {index: field, total: maxLength})"
22
22
  :autocomplete="field === 1 ? autoComplete : undefined"
@@ -0,0 +1,76 @@
1
+ <template>
2
+ <label
3
+ :class="clsx(
4
+ $style.formRadio,
5
+ disabled && $style.isDisabled,
6
+ isReadonly && $style.isReadonly,
7
+ error && $style.isInvalid
8
+ )">
9
+ <input
10
+ ref="input"
11
+ type="radio"
12
+ :class="$style.formRadioNative"
13
+ :name="group.name"
14
+ :checked="isChecked"
15
+ :disabled="disabled"
16
+ :aria-disabled="disabled ? true : undefined"
17
+ :aria-readonly="isReadonly ? true : undefined"
18
+ :aria-invalid="error ? true : undefined"
19
+ @change="onChange"
20
+ @click="onClick">
21
+
22
+ <span
23
+ aria-hidden="true"
24
+ :class="$style.formRadioElement"/>
25
+
26
+ <span
27
+ v-if="label || $slots.default"
28
+ :class="$style.formRadioLabel">
29
+ <slot>{{ label }}</slot>
30
+ </span>
31
+ </label>
32
+ </template>
33
+
34
+ <script
35
+ lang="ts"
36
+ setup>
37
+ import { clsx } from 'clsx';
38
+ import { computed, toRef, unref } from 'vue';
39
+ import { useDisabled, useFormRadioGroupInjection } from '~flux/components/composable';
40
+ import type { FluxFormRadioGroupValue } from '~flux/components/data';
41
+ import $style from '~flux/components/css/component/Form.module.scss';
42
+
43
+ const {
44
+ disabled: componentDisabled,
45
+ value
46
+ } = defineProps<{
47
+ readonly value: FluxFormRadioGroupValue;
48
+ readonly disabled?: boolean;
49
+ readonly label?: string;
50
+ }>();
51
+
52
+ defineSlots<{
53
+ default(): any;
54
+ }>();
55
+
56
+ const group = useFormRadioGroupInjection();
57
+ const disabled = useDisabled(toRef(() => componentDisabled || unref(group.disabled)));
58
+
59
+ const isChecked = computed(() => unref(group.modelValue) === value);
60
+ const isReadonly = computed(() => unref(group.isReadonly));
61
+ const error = computed(() => unref(group.error));
62
+
63
+ function onChange(): void {
64
+ if (unref(isReadonly) || unref(disabled)) {
65
+ return;
66
+ }
67
+
68
+ group.select(value);
69
+ }
70
+
71
+ function onClick(evt: MouseEvent): void {
72
+ if (unref(isReadonly)) {
73
+ evt.preventDefault();
74
+ }
75
+ }
76
+ </script>