Skip to content

Commit db85020

Browse files
committed
feat(web-core): create VtsInputWrapper component
1 parent 9b56d93 commit db85020

File tree

6 files changed

+173
-2
lines changed

6 files changed

+173
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```ts
2+
type InputWrapperMessage = MaybeArray<string | { content: string; accent?: InfoAccent }>
3+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<template>
2+
<ComponentStory
3+
v-slot="{ properties }"
4+
:params="[
5+
prop('label').str().preset('Some label').widget(),
6+
prop('learnMoreUrl').str().widget(),
7+
iconProp(),
8+
prop('message').type('InputWrapperMessage').widget(object()).preset([]).help('See presets'),
9+
slot(),
10+
slot('label').help('Can be used in place of label prop'),
11+
]"
12+
:presets
13+
>
14+
<VtsInputWrapper v-bind="properties">
15+
<UiInput accent="brand" model-value="Foo" />
16+
</VtsInputWrapper>
17+
</ComponentStory>
18+
</template>
19+
20+
<script lang="ts" setup>
21+
import ComponentStory from '@/components/component-story/ComponentStory.vue'
22+
import { iconProp, prop, slot } from '@/libs/story/story-param.ts'
23+
import { object } from '@/libs/story/story-widget.ts'
24+
import VtsInputWrapper from '@core/components/input-wrapper/VtsInputWrapper.vue'
25+
import UiInput from '@core/components/ui/input/UiInput.vue'
26+
27+
const withInfo = ['Info ']
28+
29+
const withSuccess = [
30+
...withInfo,
31+
{
32+
content: 'Success',
33+
accent: 'success',
34+
},
35+
]
36+
const withWarning = [
37+
...withSuccess,
38+
{
39+
content: 'Warning',
40+
accent: 'warning',
41+
},
42+
]
43+
const withDanger = [
44+
...withWarning,
45+
{
46+
content: 'Danger',
47+
accent: 'danger',
48+
},
49+
]
50+
51+
const presets = {
52+
'With "info" message': {
53+
props: {
54+
message: withInfo,
55+
},
56+
},
57+
'\u21B3 and "success" message': {
58+
props: {
59+
message: withSuccess,
60+
},
61+
},
62+
'\u00A0\u00A0\u21B3 and "warning" message': {
63+
props: {
64+
message: withWarning,
65+
},
66+
},
67+
'\u00A0\u00A0\u00A0\u00A0\u21B3 and "danger" message': {
68+
props: {
69+
message: withDanger,
70+
},
71+
},
72+
}
73+
</script>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<template>
2+
<div class="vts-input-wrapper">
3+
<UiLabel
4+
v-if="slots.label || label"
5+
:accent="labelAccent"
6+
:for="id"
7+
:href="learnMoreUrl"
8+
:icon
9+
:required="wrapperController.required"
10+
>
11+
<slot name="label">{{ label }}</slot>
12+
</UiLabel>
13+
<slot />
14+
<UiInfo v-for="{ content, accent } of messages" :key="content" :accent="accent">
15+
{{ content }}
16+
</UiInfo>
17+
</div>
18+
</template>
19+
20+
<script lang="ts" setup>
21+
import UiInfo, { type InfoAccent } from '@core/components/ui/info/UiInfo.vue'
22+
import UiLabel, { type LabelAccent } from '@core/components/ui/label/UiLabel.vue'
23+
import { useMapper } from '@core/composables/mapper.composable'
24+
import { useRanked } from '@core/composables/ranked.composable.ts'
25+
import type { MaybeArray } from '@core/types/utility.type'
26+
import { IK_INPUT_WRAPPER_CONTROLLER } from '@core/utils/injection-keys.util'
27+
import { toArray } from '@core/utils/to-array.utils'
28+
import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
29+
import { useArrayMap } from '@vueuse/core'
30+
import { provide, reactive, useId } from 'vue'
31+
32+
export type InputWrapperMessage = MaybeArray<string | { content: string; accent?: InfoAccent }>
33+
34+
export type InputWrapperController = {
35+
id: string
36+
labelAccent: LabelAccent
37+
required: boolean
38+
}
39+
40+
const { message: _message } = defineProps<{
41+
label?: string
42+
learnMoreUrl?: string
43+
icon?: IconDefinition
44+
message?: InputWrapperMessage
45+
}>()
46+
47+
const slots = defineSlots<{
48+
default(): any
49+
label?(): any
50+
}>()
51+
52+
const id = useId()
53+
54+
const unsortedMessages = useArrayMap(
55+
() => toArray(_message),
56+
item => ({
57+
content: typeof item === 'object' ? item.content : item,
58+
accent: typeof item === 'object' ? (item.accent ?? 'info') : 'info',
59+
})
60+
)
61+
62+
const messages = useRanked(unsortedMessages, ({ accent }) => accent, ['danger', 'warning', 'success', 'info'])
63+
64+
const labelAccent = useMapper<InfoAccent, LabelAccent>(
65+
() => messages.value[0]?.accent,
66+
{
67+
info: 'neutral',
68+
success: 'neutral',
69+
warning: 'warning',
70+
danger: 'danger',
71+
},
72+
'neutral'
73+
)
74+
75+
const wrapperController = reactive({
76+
id,
77+
labelAccent,
78+
required: false,
79+
}) satisfies InputWrapperController
80+
81+
provide(IK_INPUT_WRAPPER_CONTROLLER, wrapperController)
82+
</script>
83+
84+
<style lang="postcss" scoped>
85+
.vts-input-wrapper {
86+
display: flex;
87+
flex-direction: column;
88+
gap: 0.4rem;
89+
}
90+
</style>

@xen-orchestra/web-core/lib/components/ui/label/UiLabel.vue

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ import UiLink from '@core/components/ui/link/UiLink.vue'
1515
import { toVariants } from '@core/utils/to-variants.util'
1616
import type { IconDefinition } from '@fortawesome/fontawesome-common-types'
1717

18+
export type LabelAccent = 'neutral' | 'warning' | 'danger'
19+
1820
const { for: htmlFor } = defineProps<{
19-
accent: 'neutral' | 'warning' | 'danger'
21+
accent: LabelAccent
2022
for?: string
2123
icon?: IconDefinition
2224
required?: boolean

@xen-orchestra/web-core/lib/utils/if-else.utils.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { MaybeArray, VoidFunction } from '@core/types/utility.type'
22
import { toArray } from '@core/utils/to-array.utils'
33
import { watch, type WatchOptions, type WatchSource } from 'vue'
44

5-
export interface IfElseOptions extends Pick<WatchOptions, 'immediate'> {}
5+
export interface IfElseOptions extends Pick<WatchOptions, 'immediate' | 'flush'> {}
66

77
export function ifElse(
88
source: WatchSource<boolean>,

@xen-orchestra/web-core/lib/utils/injection-keys.util.ts

+3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { InputWrapperController } from '@core/components/input-wrapper/VtsInputWrapper.vue'
12
import type { ValueFormatter } from '@core/types/chart'
23
import type { ComputedRef, InjectionKey, Ref } from 'vue'
34

@@ -18,3 +19,5 @@ export const IK_CLOSE_MENU = Symbol('IK_CLOSE_MENU') as InjectionKey<() => void>
1819
export const IK_MENU_TELEPORTED = Symbol('IK_MENU_TELEPORTED') as InjectionKey<boolean>
1920

2021
export const IK_DISABLED = Symbol('IK_DISABLED') as InjectionKey<ComputedRef<boolean>>
22+
23+
export const IK_INPUT_WRAPPER_CONTROLLER = Symbol('IK_INPUT_WRAPPER_CONTROLLER') as InjectionKey<InputWrapperController>

0 commit comments

Comments
 (0)