@polymarbot/nuxt-layer-shadcn-ui 0.8.7 → 0.9.0
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/app/components/ui/Checkbox/index.stories.ts +9 -0
- package/app/components/ui/Checkbox/index.vue +4 -1
- package/app/components/ui/Checkbox/types.ts +3 -1
- package/app/components/ui/DatePicker/index.stories.ts +9 -0
- package/app/components/ui/DatePicker/index.vue +3 -0
- package/app/components/ui/DatePicker/types.ts +2 -0
- package/app/components/ui/DateRangePicker/index.stories.ts +9 -0
- package/app/components/ui/DateRangePicker/index.vue +3 -0
- package/app/components/ui/DateRangePicker/types.ts +2 -0
- package/app/components/ui/Drawer/index.stories.ts +78 -1
- package/app/components/ui/Drawer/index.vue +31 -9
- package/app/components/ui/Drawer/types.ts +5 -0
- package/app/components/ui/FormItem/index.stories.ts +137 -10
- package/app/components/ui/FormItem/index.vue +10 -3
- package/app/components/ui/Input/index.vue +3 -1
- package/app/components/ui/InputCurrency/index.stories.ts +9 -0
- package/app/components/ui/InputCurrency/types.ts +3 -1
- package/app/components/ui/InputNumber/index.vue +10 -7
- package/app/components/ui/InputOtp/index.stories.ts +10 -0
- package/app/components/ui/InputOtp/index.vue +5 -1
- package/app/components/ui/InputOtp/types.ts +1 -0
- package/app/components/ui/InputPercent/index.stories.ts +11 -0
- package/app/components/ui/InputPercent/index.vue +6 -0
- package/app/components/ui/InputPercent/types.ts +3 -0
- package/app/components/ui/InputRange/index.stories.ts +11 -0
- package/app/components/ui/Modal/index.stories.ts +78 -0
- package/app/components/ui/Modal/index.vue +31 -9
- package/app/components/ui/Modal/types.ts +5 -0
- package/app/components/ui/RadioCardGroup/index.stories.ts +9 -0
- package/app/components/ui/RadioCardGroup/index.vue +4 -0
- package/app/components/ui/RadioCardGroup/types.ts +1 -0
- package/app/components/ui/RadioGroup/index.stories.ts +9 -0
- package/app/components/ui/RadioGroup/index.vue +4 -0
- package/app/components/ui/RadioGroup/types.ts +1 -0
- package/app/components/ui/SearchSelect/index.stories.ts +9 -0
- package/app/components/ui/SearchSelect/index.vue +2 -0
- package/app/components/ui/SearchSelect/types.ts +1 -0
- package/app/components/ui/Select/index.stories.ts +9 -0
- package/app/components/ui/Select/index.vue +6 -0
- package/app/components/ui/Select/types.ts +1 -0
- package/app/components/ui/Textarea/index.vue +3 -1
- package/app/composables/useFormItemInvalid.ts +16 -0
- package/package.json +2 -2
|
@@ -28,26 +28,29 @@ const model = computed({
|
|
|
28
28
|
set: value => emit('update:modelValue', value),
|
|
29
29
|
})
|
|
30
30
|
|
|
31
|
+
const isInvalid = useFormItemInvalid(() => props.invalid)
|
|
32
|
+
|
|
31
33
|
const contentClass = computed(() =>
|
|
32
34
|
cn(
|
|
33
35
|
`
|
|
34
|
-
|
|
35
|
-
transition-[color,box-shadow]
|
|
36
|
+
h-9 rounded-md border-input shadow-xs
|
|
36
37
|
dark:bg-input/30
|
|
38
|
+
flex items-center border transition-[color,box-shadow]
|
|
37
39
|
`,
|
|
38
40
|
`
|
|
39
41
|
has-[[data-slot=input]:focus-visible]:border-ring
|
|
40
|
-
has-[[data-slot=input]:focus-visible]:ring-[3px]
|
|
41
42
|
has-[[data-slot=input]:focus-visible]:ring-ring/50
|
|
43
|
+
has-[[data-slot=input]:focus-visible]:ring-[3px]
|
|
42
44
|
`,
|
|
43
|
-
|
|
45
|
+
isInvalid.value && `
|
|
44
46
|
border-destructive ring-destructive/20
|
|
45
47
|
dark:ring-destructive/40
|
|
46
48
|
`,
|
|
47
49
|
),
|
|
48
50
|
)
|
|
49
51
|
|
|
50
|
-
const
|
|
52
|
+
const buttonClass = 'static translate-y-0 shrink-0 cursor-pointer'
|
|
53
|
+
const inputClass = 'flex-1 min-w-0 border-0 shadow-none focus-visible:ring-0 rounded-none'
|
|
51
54
|
</script>
|
|
52
55
|
|
|
53
56
|
<template>
|
|
@@ -61,7 +64,7 @@ const inputClass = 'flex-1 border-0 shadow-none focus-visible:ring-0 rounded-non
|
|
|
61
64
|
<NumberFieldContent :class="contentClass">
|
|
62
65
|
<NumberFieldDecrement
|
|
63
66
|
v-if="showButtons"
|
|
64
|
-
class="
|
|
67
|
+
:class="buttonClass"
|
|
65
68
|
/>
|
|
66
69
|
<NumberFieldInput
|
|
67
70
|
:placeholder="placeholder"
|
|
@@ -69,7 +72,7 @@ const inputClass = 'flex-1 border-0 shadow-none focus-visible:ring-0 rounded-non
|
|
|
69
72
|
/>
|
|
70
73
|
<NumberFieldIncrement
|
|
71
74
|
v-if="showButtons"
|
|
72
|
-
class="
|
|
75
|
+
:class="buttonClass"
|
|
73
76
|
/>
|
|
74
77
|
</NumberFieldContent>
|
|
75
78
|
</NumberField>
|
|
@@ -10,11 +10,13 @@ const meta = {
|
|
|
10
10
|
modelValue: { control: 'text' },
|
|
11
11
|
length: { control: 'number' },
|
|
12
12
|
disabled: { control: 'boolean' },
|
|
13
|
+
invalid: { control: 'boolean' },
|
|
13
14
|
},
|
|
14
15
|
args: {
|
|
15
16
|
modelValue: '',
|
|
16
17
|
length: 6,
|
|
17
18
|
disabled: false,
|
|
19
|
+
invalid: false,
|
|
18
20
|
},
|
|
19
21
|
render: args => {
|
|
20
22
|
const onUpdate = useArgsModel()
|
|
@@ -53,6 +55,14 @@ export const Disabled: Story = {
|
|
|
53
55
|
},
|
|
54
56
|
}
|
|
55
57
|
|
|
58
|
+
export const Invalid: Story = {
|
|
59
|
+
parameters: noControls,
|
|
60
|
+
args: {
|
|
61
|
+
invalid: true,
|
|
62
|
+
modelValue: '123456',
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
56
66
|
export const EventHandling: Story = {
|
|
57
67
|
parameters: noControls,
|
|
58
68
|
render: () => ({
|
|
@@ -6,10 +6,11 @@ import {
|
|
|
6
6
|
} from '../../shadcn/pin-input'
|
|
7
7
|
import type { InputOtpProps } from './types'
|
|
8
8
|
|
|
9
|
-
withDefaults(defineProps<InputOtpProps>(), {
|
|
9
|
+
const props = withDefaults(defineProps<InputOtpProps>(), {
|
|
10
10
|
modelValue: '',
|
|
11
11
|
length: 6,
|
|
12
12
|
disabled: false,
|
|
13
|
+
invalid: false,
|
|
13
14
|
})
|
|
14
15
|
|
|
15
16
|
const emit = defineEmits<{
|
|
@@ -17,6 +18,8 @@ const emit = defineEmits<{
|
|
|
17
18
|
'complete': [value: string]
|
|
18
19
|
}>()
|
|
19
20
|
|
|
21
|
+
const isInvalid = useFormItemInvalid(() => props.invalid)
|
|
22
|
+
|
|
20
23
|
function handleComplete (values: string[]) {
|
|
21
24
|
emit('complete', values.join(''))
|
|
22
25
|
}
|
|
@@ -39,6 +42,7 @@ function handleUpdate (value: string[]) {
|
|
|
39
42
|
v-for="(_, index) in length"
|
|
40
43
|
:key="index"
|
|
41
44
|
:index="index"
|
|
45
|
+
:aria-invalid="isInvalid || undefined"
|
|
42
46
|
/>
|
|
43
47
|
</PinInputGroup>
|
|
44
48
|
</PinInput>
|
|
@@ -7,9 +7,11 @@ const meta = {
|
|
|
7
7
|
component: InputPercent as any,
|
|
8
8
|
argTypes: {
|
|
9
9
|
modelValue: { control: 'number' },
|
|
10
|
+
invalid: { control: 'boolean' },
|
|
10
11
|
},
|
|
11
12
|
args: {
|
|
12
13
|
modelValue: 0.5,
|
|
14
|
+
invalid: false,
|
|
13
15
|
},
|
|
14
16
|
render: args => {
|
|
15
17
|
const onUpdate = useArgsModel()
|
|
@@ -29,4 +31,13 @@ const meta = {
|
|
|
29
31
|
export default meta
|
|
30
32
|
type Story = StoryObj<typeof meta>
|
|
31
33
|
|
|
34
|
+
const noControls = { controls: { disable: true }} satisfies Story['parameters']
|
|
35
|
+
|
|
32
36
|
export const Default: Story = {}
|
|
37
|
+
|
|
38
|
+
export const Invalid: Story = {
|
|
39
|
+
parameters: noControls,
|
|
40
|
+
args: {
|
|
41
|
+
invalid: true,
|
|
42
|
+
},
|
|
43
|
+
}
|
|
@@ -15,6 +15,7 @@ const meta = {
|
|
|
15
15
|
startPlaceholder: { control: 'text' },
|
|
16
16
|
endPlaceholder: { control: 'text' },
|
|
17
17
|
disabled: { control: 'boolean' },
|
|
18
|
+
invalid: { control: 'boolean' },
|
|
18
19
|
as: { control: false },
|
|
19
20
|
},
|
|
20
21
|
args: {
|
|
@@ -25,6 +26,7 @@ const meta = {
|
|
|
25
26
|
startPlaceholder: '',
|
|
26
27
|
endPlaceholder: '',
|
|
27
28
|
disabled: false,
|
|
29
|
+
invalid: false,
|
|
28
30
|
as: undefined,
|
|
29
31
|
},
|
|
30
32
|
render: args => {
|
|
@@ -71,6 +73,15 @@ export const Disabled: Story = {
|
|
|
71
73
|
},
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
export const Invalid: Story = {
|
|
77
|
+
parameters: noControls,
|
|
78
|
+
args: {
|
|
79
|
+
invalid: true,
|
|
80
|
+
start: 20,
|
|
81
|
+
end: 80,
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
74
85
|
export const CustomInput: Story = {
|
|
75
86
|
parameters: {
|
|
76
87
|
...noControls,
|
|
@@ -4,6 +4,7 @@ import EventLog from '#storybook/EventLog.vue'
|
|
|
4
4
|
import { useArgsModel } from '#storybook/argsModel'
|
|
5
5
|
import Button from '../Button/index.vue'
|
|
6
6
|
import Input from '../Input/index.vue'
|
|
7
|
+
import type { ModalAction } from './types'
|
|
7
8
|
import Modal from './index.vue'
|
|
8
9
|
|
|
9
10
|
const types: ModalContentType[] = [ 'default', 'success', 'info', 'help', 'warn', 'danger', 'error' ]
|
|
@@ -255,6 +256,83 @@ export const WithTrigger: Story = {
|
|
|
255
256
|
}),
|
|
256
257
|
}
|
|
257
258
|
|
|
259
|
+
export const PreventClose: Story = {
|
|
260
|
+
parameters: {
|
|
261
|
+
...noControls,
|
|
262
|
+
docs: {
|
|
263
|
+
source: {
|
|
264
|
+
code: `
|
|
265
|
+
<template>
|
|
266
|
+
<Modal
|
|
267
|
+
v-model:visible="visible"
|
|
268
|
+
title="Type to Continue"
|
|
269
|
+
description="beforeClose intercepts the confirm action; cancel/X/ESC close normally."
|
|
270
|
+
showCancel
|
|
271
|
+
confirmText="Submit"
|
|
272
|
+
:beforeClose="onBeforeClose"
|
|
273
|
+
>
|
|
274
|
+
<Input v-model="value" placeholder="Type 'confirm' to close" />
|
|
275
|
+
<p v-if="error" class="mt-2 text-sm text-destructive">{{ error }}</p>
|
|
276
|
+
</Modal>
|
|
277
|
+
</template>
|
|
278
|
+
|
|
279
|
+
<script setup lang="ts">
|
|
280
|
+
import type { ModalAction } from '#components'
|
|
281
|
+
|
|
282
|
+
const visible = ref(false)
|
|
283
|
+
const value = ref('')
|
|
284
|
+
const error = ref('')
|
|
285
|
+
|
|
286
|
+
function onBeforeClose (action: ModalAction) {
|
|
287
|
+
if (action === 'cancel') return
|
|
288
|
+
if (value.value !== 'confirm') {
|
|
289
|
+
error.value = "Value must be 'confirm' to close."
|
|
290
|
+
return false
|
|
291
|
+
}
|
|
292
|
+
error.value = ''
|
|
293
|
+
return new Promise(resolve => setTimeout(resolve, 1000))
|
|
294
|
+
}
|
|
295
|
+
</script>
|
|
296
|
+
`.trim(),
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
render: () => ({
|
|
301
|
+
components: { Modal, Button, Input },
|
|
302
|
+
setup () {
|
|
303
|
+
const visible = ref(false)
|
|
304
|
+
const value = ref('')
|
|
305
|
+
const error = ref('')
|
|
306
|
+
function onBeforeClose (action: ModalAction) {
|
|
307
|
+
if (action === 'cancel') return
|
|
308
|
+
if (value.value !== 'confirm') {
|
|
309
|
+
error.value = 'Value must be "confirm" to close.'
|
|
310
|
+
return false
|
|
311
|
+
}
|
|
312
|
+
error.value = ''
|
|
313
|
+
return new Promise<void>(resolve => setTimeout(resolve, 1000))
|
|
314
|
+
}
|
|
315
|
+
return { visible, value, error, onBeforeClose }
|
|
316
|
+
},
|
|
317
|
+
template: `
|
|
318
|
+
<div>
|
|
319
|
+
<Button @click="visible = true">Open Modal</Button>
|
|
320
|
+
<Modal
|
|
321
|
+
v-model:visible="visible"
|
|
322
|
+
title="Type to Continue"
|
|
323
|
+
description="beforeClose intercepts the confirm action; cancel/X/ESC close normally."
|
|
324
|
+
showCancel
|
|
325
|
+
confirmText="Submit"
|
|
326
|
+
:beforeClose="onBeforeClose"
|
|
327
|
+
>
|
|
328
|
+
<Input v-model="value" placeholder="Type 'confirm' to close" />
|
|
329
|
+
<p v-if="error" class="mt-2 text-sm text-destructive">{{ error }}</p>
|
|
330
|
+
</Modal>
|
|
331
|
+
</div>
|
|
332
|
+
`,
|
|
333
|
+
}),
|
|
334
|
+
}
|
|
335
|
+
|
|
258
336
|
export const EventHandling: Story = {
|
|
259
337
|
parameters: {
|
|
260
338
|
...noControls,
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
DialogTitle,
|
|
10
10
|
DialogTrigger,
|
|
11
11
|
} from '../../shadcn/dialog'
|
|
12
|
-
import type { ModalProps } from './types'
|
|
12
|
+
import type { ModalAction, ModalProps } from './types'
|
|
13
13
|
|
|
14
14
|
defineOptions({ inheritAttrs: false })
|
|
15
15
|
|
|
@@ -25,6 +25,7 @@ const props = withDefaults(defineProps<ModalProps>(), {
|
|
|
25
25
|
content: undefined,
|
|
26
26
|
confirmVariant: 'default',
|
|
27
27
|
cancelVariant: 'outline',
|
|
28
|
+
beforeClose: undefined,
|
|
28
29
|
type: undefined,
|
|
29
30
|
class: undefined,
|
|
30
31
|
})
|
|
@@ -48,6 +49,8 @@ const resolvedCancelText = computed(
|
|
|
48
49
|
)
|
|
49
50
|
|
|
50
51
|
const dialogOpen = ref(props.visible ?? false)
|
|
52
|
+
const internalLoading = ref(false)
|
|
53
|
+
const isLoading = computed(() => internalLoading.value || props.loading)
|
|
51
54
|
|
|
52
55
|
watch(() => props.visible, value => {
|
|
53
56
|
if (value !== undefined) dialogOpen.value = value
|
|
@@ -60,18 +63,37 @@ watch(dialogOpen, value => {
|
|
|
60
63
|
})
|
|
61
64
|
|
|
62
65
|
function onOpenUpdate (value: boolean) {
|
|
63
|
-
if (!value &&
|
|
66
|
+
if (!value && isLoading.value) return
|
|
64
67
|
if (value) dialogOpen.value = true
|
|
65
|
-
else
|
|
68
|
+
else handleClose('cancel')
|
|
66
69
|
}
|
|
67
70
|
|
|
68
71
|
function onConfirm () {
|
|
69
72
|
emit('confirm')
|
|
70
|
-
|
|
73
|
+
handleClose('confirm')
|
|
71
74
|
}
|
|
72
75
|
|
|
73
76
|
function onCancel () {
|
|
74
77
|
emit('cancel')
|
|
78
|
+
handleClose('cancel')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function handleClose (action: ModalAction) {
|
|
82
|
+
if (!props.beforeClose) {
|
|
83
|
+
dialogOpen.value = false
|
|
84
|
+
return
|
|
85
|
+
}
|
|
86
|
+
const result = props.beforeClose(action)
|
|
87
|
+
if (result === false) return
|
|
88
|
+
if (result instanceof Promise) {
|
|
89
|
+
internalLoading.value = true
|
|
90
|
+
result.then(() => {
|
|
91
|
+
dialogOpen.value = false
|
|
92
|
+
}).finally(() => {
|
|
93
|
+
internalLoading.value = false
|
|
94
|
+
})
|
|
95
|
+
return
|
|
96
|
+
}
|
|
75
97
|
dialogOpen.value = false
|
|
76
98
|
}
|
|
77
99
|
|
|
@@ -143,8 +165,8 @@ const contentClass = computed(() =>
|
|
|
143
165
|
<ModalContent
|
|
144
166
|
:type="type"
|
|
145
167
|
:content="content"
|
|
146
|
-
:inert="
|
|
147
|
-
:class="[
|
|
168
|
+
:inert="isLoading || disabled || undefined"
|
|
169
|
+
:class="[ isLoading || disabled ? 'opacity-50' : undefined ]"
|
|
148
170
|
class="p-1"
|
|
149
171
|
>
|
|
150
172
|
<slot />
|
|
@@ -171,7 +193,7 @@ const contentClass = computed(() =>
|
|
|
171
193
|
v-if="showCancel"
|
|
172
194
|
class="min-w-32"
|
|
173
195
|
:variant="cancelVariant"
|
|
174
|
-
:disabled="
|
|
196
|
+
:disabled="isLoading"
|
|
175
197
|
@click="onCancel"
|
|
176
198
|
>
|
|
177
199
|
{{ resolvedCancelText }}
|
|
@@ -179,7 +201,7 @@ const contentClass = computed(() =>
|
|
|
179
201
|
<Button
|
|
180
202
|
:class="showCancel ? 'min-w-32' : 'min-w-48'"
|
|
181
203
|
:variant="confirmVariant"
|
|
182
|
-
:loading="
|
|
204
|
+
:loading="isLoading"
|
|
183
205
|
:disabled="disabled || confirmDisabled"
|
|
184
206
|
@click="onConfirm"
|
|
185
207
|
>
|
|
@@ -191,7 +213,7 @@ const contentClass = computed(() =>
|
|
|
191
213
|
|
|
192
214
|
<DialogClose
|
|
193
215
|
v-if="showClose"
|
|
194
|
-
:disabled="
|
|
216
|
+
:disabled="isLoading"
|
|
195
217
|
class="
|
|
196
218
|
top-3 right-3 size-8 text-muted-foreground ring-offset-background
|
|
197
219
|
hover:bg-accent/50 hover:text-foreground
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { ButtonVariants } from '../../shadcn/button'
|
|
2
2
|
import type { ModalContentProps } from '../ModalContent/types'
|
|
3
3
|
|
|
4
|
+
export type ModalAction = 'confirm' | 'cancel'
|
|
5
|
+
|
|
6
|
+
export type ModalBeforeClose = (action: ModalAction) => boolean | undefined | Promise<unknown>
|
|
7
|
+
|
|
4
8
|
export interface ModalProps {
|
|
5
9
|
visible?: boolean
|
|
6
10
|
loading?: boolean
|
|
@@ -20,6 +24,7 @@ export interface ModalProps {
|
|
|
20
24
|
cancelText?: string
|
|
21
25
|
confirmVariant?: ButtonVariants['variant']
|
|
22
26
|
cancelVariant?: ButtonVariants['variant']
|
|
27
|
+
beforeClose?: ModalBeforeClose
|
|
23
28
|
type?: ModalContentProps['type']
|
|
24
29
|
class?: ClassValue
|
|
25
30
|
}
|
|
@@ -43,11 +43,13 @@ const meta = {
|
|
|
43
43
|
modelValue: { control: 'text' },
|
|
44
44
|
options: { control: 'object' },
|
|
45
45
|
disabled: { control: 'boolean' },
|
|
46
|
+
invalid: { control: 'boolean' },
|
|
46
47
|
},
|
|
47
48
|
args: {
|
|
48
49
|
modelValue: 'current',
|
|
49
50
|
options,
|
|
50
51
|
disabled: false,
|
|
52
|
+
invalid: false,
|
|
51
53
|
},
|
|
52
54
|
render: args => {
|
|
53
55
|
const onUpdate = useArgsModel()
|
|
@@ -106,6 +108,13 @@ export const Disabled: Story = {
|
|
|
106
108
|
},
|
|
107
109
|
}
|
|
108
110
|
|
|
111
|
+
export const Invalid: Story = {
|
|
112
|
+
parameters: noControls,
|
|
113
|
+
args: {
|
|
114
|
+
invalid: true,
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
|
|
109
118
|
export const EventHandling: Story = {
|
|
110
119
|
parameters: {
|
|
111
120
|
...noControls,
|
|
@@ -8,9 +8,12 @@ import type { RadioCardGroupProps } from './types'
|
|
|
8
8
|
const props = withDefaults(defineProps<RadioCardGroupProps>(), {
|
|
9
9
|
modelValue: undefined,
|
|
10
10
|
disabled: false,
|
|
11
|
+
invalid: false,
|
|
11
12
|
class: undefined,
|
|
12
13
|
})
|
|
13
14
|
|
|
15
|
+
const isInvalid = useFormItemInvalid(() => props.invalid)
|
|
16
|
+
|
|
14
17
|
const emit = defineEmits<{
|
|
15
18
|
'update:modelValue': [value: string]
|
|
16
19
|
}>()
|
|
@@ -47,6 +50,7 @@ const mergedClass = computed(() => cn('gap-3', props.class))
|
|
|
47
50
|
<ShadcnRadioGroupItem
|
|
48
51
|
:value="option.value"
|
|
49
52
|
:disabled="option.disabled"
|
|
53
|
+
:aria-invalid="isInvalid || undefined"
|
|
50
54
|
/>
|
|
51
55
|
<div class="gap-0.5 grid flex-1">
|
|
52
56
|
<span class="text-sm font-medium">
|
|
@@ -23,12 +23,14 @@ const meta = {
|
|
|
23
23
|
items: { control: 'object' },
|
|
24
24
|
modelValue: { control: 'text' },
|
|
25
25
|
disabled: { control: 'boolean' },
|
|
26
|
+
invalid: { control: 'boolean' },
|
|
26
27
|
orientation: { control: 'inline-radio', options: [ 'vertical', 'horizontal' ]},
|
|
27
28
|
},
|
|
28
29
|
args: {
|
|
29
30
|
items: options,
|
|
30
31
|
modelValue: 'option1',
|
|
31
32
|
disabled: false,
|
|
33
|
+
invalid: false,
|
|
32
34
|
orientation: 'vertical',
|
|
33
35
|
},
|
|
34
36
|
render: args => {
|
|
@@ -70,6 +72,13 @@ export const Disabled: Story = {
|
|
|
70
72
|
},
|
|
71
73
|
}
|
|
72
74
|
|
|
75
|
+
export const Invalid: Story = {
|
|
76
|
+
parameters: noControls,
|
|
77
|
+
args: {
|
|
78
|
+
invalid: true,
|
|
79
|
+
},
|
|
80
|
+
}
|
|
81
|
+
|
|
73
82
|
export const CustomSlots: Story = {
|
|
74
83
|
parameters: {
|
|
75
84
|
...noControls,
|
|
@@ -9,10 +9,13 @@ const props = withDefaults(defineProps<RadioGroupProps>(), {
|
|
|
9
9
|
items: () => [],
|
|
10
10
|
modelValue: undefined,
|
|
11
11
|
disabled: false,
|
|
12
|
+
invalid: false,
|
|
12
13
|
orientation: 'vertical',
|
|
13
14
|
class: undefined,
|
|
14
15
|
})
|
|
15
16
|
|
|
17
|
+
const isInvalid = useFormItemInvalid(() => props.invalid)
|
|
18
|
+
|
|
16
19
|
const emit = defineEmits<{
|
|
17
20
|
'update:modelValue': [value: string]
|
|
18
21
|
}>()
|
|
@@ -58,6 +61,7 @@ const mergedClass = computed(() => cn(
|
|
|
58
61
|
<ShadcnRadioGroupItem
|
|
59
62
|
:value="item.value"
|
|
60
63
|
:disabled="item.disabled"
|
|
64
|
+
:aria-invalid="isInvalid || undefined"
|
|
61
65
|
/>
|
|
62
66
|
<slot
|
|
63
67
|
name="label"
|
|
@@ -52,6 +52,7 @@ const meta = {
|
|
|
52
52
|
loadLimit: { control: 'number' },
|
|
53
53
|
autoLoad: { control: 'boolean' },
|
|
54
54
|
disabled: { control: 'boolean' },
|
|
55
|
+
invalid: { control: 'boolean' },
|
|
55
56
|
},
|
|
56
57
|
args: {
|
|
57
58
|
modelValue: undefined,
|
|
@@ -64,6 +65,7 @@ const meta = {
|
|
|
64
65
|
loadLimit: 20,
|
|
65
66
|
autoLoad: false,
|
|
66
67
|
disabled: false,
|
|
68
|
+
invalid: false,
|
|
67
69
|
},
|
|
68
70
|
render: args => {
|
|
69
71
|
const onUpdate = useArgsModel()
|
|
@@ -182,6 +184,13 @@ export const Disabled: Story = {
|
|
|
182
184
|
},
|
|
183
185
|
}
|
|
184
186
|
|
|
187
|
+
export const Invalid: Story = {
|
|
188
|
+
parameters: noControls,
|
|
189
|
+
args: {
|
|
190
|
+
invalid: true,
|
|
191
|
+
},
|
|
192
|
+
}
|
|
193
|
+
|
|
185
194
|
export const EventHandling: Story = {
|
|
186
195
|
parameters: {
|
|
187
196
|
...noControls,
|
|
@@ -16,6 +16,7 @@ const props = withDefaults(defineProps<SearchSelectProps<TValue, TMeta>>(), {
|
|
|
16
16
|
loadLimit: 20,
|
|
17
17
|
placeholder: undefined,
|
|
18
18
|
disabled: false,
|
|
19
|
+
invalid: false,
|
|
19
20
|
searchPlaceholder: undefined,
|
|
20
21
|
emptyText: undefined,
|
|
21
22
|
searchEmptyText: undefined,
|
|
@@ -191,6 +192,7 @@ defineExpose({ refresh: resetAndLoad })
|
|
|
191
192
|
:searchPlaceholder="searchPlaceholder"
|
|
192
193
|
:emptyText="computedEmptyText"
|
|
193
194
|
:disabled="disabled"
|
|
195
|
+
:invalid="invalid"
|
|
194
196
|
:loading="isLoading"
|
|
195
197
|
@search="handleSearch"
|
|
196
198
|
@open="handleOpen"
|
|
@@ -27,6 +27,7 @@ export interface SearchSelectProps<V extends string | number = string, M = unkno
|
|
|
27
27
|
loadLimit?: number
|
|
28
28
|
placeholder?: string
|
|
29
29
|
disabled?: boolean
|
|
30
|
+
invalid?: boolean
|
|
30
31
|
searchPlaceholder?: string
|
|
31
32
|
/** Message when no options available (no search keyword) */
|
|
32
33
|
emptyText?: string
|
|
@@ -38,6 +38,7 @@ const meta = {
|
|
|
38
38
|
modelValue: { control: 'text' },
|
|
39
39
|
placeholder: { control: 'text' },
|
|
40
40
|
disabled: { control: 'boolean' },
|
|
41
|
+
invalid: { control: 'boolean' },
|
|
41
42
|
loading: { control: 'boolean' },
|
|
42
43
|
filter: { control: 'boolean' },
|
|
43
44
|
multiple: { control: 'boolean' },
|
|
@@ -48,6 +49,7 @@ const meta = {
|
|
|
48
49
|
modelValue: undefined,
|
|
49
50
|
placeholder: 'Select an option',
|
|
50
51
|
disabled: false,
|
|
52
|
+
invalid: false,
|
|
51
53
|
loading: false,
|
|
52
54
|
filter: false,
|
|
53
55
|
multiple: false,
|
|
@@ -290,6 +292,13 @@ export const Loading: Story = {
|
|
|
290
292
|
},
|
|
291
293
|
}
|
|
292
294
|
|
|
295
|
+
export const Invalid: Story = {
|
|
296
|
+
parameters: noControls,
|
|
297
|
+
args: {
|
|
298
|
+
invalid: true,
|
|
299
|
+
},
|
|
300
|
+
}
|
|
301
|
+
|
|
293
302
|
export const EventHandling: Story = {
|
|
294
303
|
parameters: noControls,
|
|
295
304
|
render: () => ({
|
|
@@ -29,6 +29,7 @@ const props = withDefaults(defineProps<SelectProps<TValue, TMeta>>(), {
|
|
|
29
29
|
modelValue: undefined,
|
|
30
30
|
placeholder: undefined,
|
|
31
31
|
disabled: false,
|
|
32
|
+
invalid: false,
|
|
32
33
|
loading: false,
|
|
33
34
|
filter: false,
|
|
34
35
|
searchPlaceholder: undefined,
|
|
@@ -36,6 +37,8 @@ const props = withDefaults(defineProps<SelectProps<TValue, TMeta>>(), {
|
|
|
36
37
|
multiple: false,
|
|
37
38
|
})
|
|
38
39
|
|
|
40
|
+
const isInvalid = useFormItemInvalid(() => props.invalid)
|
|
41
|
+
|
|
39
42
|
const emit = defineEmits<{
|
|
40
43
|
'update:modelValue': [value: TValue | TValue[]]
|
|
41
44
|
'search': [value: string]
|
|
@@ -154,10 +157,13 @@ function handleClear (event: MouseEvent) {
|
|
|
154
157
|
role="combobox"
|
|
155
158
|
tabindex="0"
|
|
156
159
|
:aria-expanded="open"
|
|
160
|
+
:aria-invalid="isInvalid || undefined"
|
|
157
161
|
:data-disabled="disabled || undefined"
|
|
158
162
|
:data-state="open ? 'open' : 'closed'"
|
|
159
163
|
class="
|
|
160
164
|
data-[state=open]:border-ring data-[state=open]:ring-ring/50
|
|
165
|
+
aria-invalid:ring-destructive/20 aria-invalid:border-destructive
|
|
166
|
+
dark:aria-invalid:ring-destructive/40
|
|
161
167
|
cursor-pointer
|
|
162
168
|
data-[state=open]:ring-[3px]
|
|
163
169
|
"
|
|
@@ -13,6 +13,7 @@ export type SelectBaseProps<V extends string | number = string, M = unknown> = {
|
|
|
13
13
|
options?: SelectOption<V, M>[]
|
|
14
14
|
placeholder?: string
|
|
15
15
|
disabled?: boolean
|
|
16
|
+
invalid?: boolean
|
|
16
17
|
/** Show a spinner in place of the chevron */
|
|
17
18
|
loading?: boolean
|
|
18
19
|
/** true: enable client-side label filter; function: custom filter (disables internal filter) */
|
|
@@ -17,6 +17,8 @@ const emit = defineEmits<{
|
|
|
17
17
|
'change': [value: string]
|
|
18
18
|
}>()
|
|
19
19
|
|
|
20
|
+
const isInvalid = useFormItemInvalid(() => props.invalid)
|
|
21
|
+
|
|
20
22
|
function handleInput (event: Event) {
|
|
21
23
|
const target = event.target as HTMLTextAreaElement
|
|
22
24
|
emit('update:modelValue', target.value)
|
|
@@ -40,7 +42,7 @@ const mergedClass = computed(() =>
|
|
|
40
42
|
:modelValue="modelValue"
|
|
41
43
|
:rows="rows"
|
|
42
44
|
:class="mergedClass"
|
|
43
|
-
:aria-invalid="
|
|
45
|
+
:aria-invalid="isInvalid || undefined"
|
|
44
46
|
:data-1p-ignore="autocomplete === 'off' || !autocomplete ? true : undefined"
|
|
45
47
|
:autocomplete="autocomplete || 'off'"
|
|
46
48
|
v-bind="$attrs"
|