@soave/ui 0.2.2 → 0.3.1
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/dist/components/Button.vue +36 -0
- package/dist/components/Card.vue +23 -0
- package/dist/components/Checkbox.vue +44 -0
- package/dist/components/Dialog.vue +99 -0
- package/dist/components/Input.vue +48 -0
- package/dist/components/RadioGroup.vue +35 -0
- package/dist/components/RadioItem.vue +55 -0
- package/dist/components/Select.vue +95 -0
- package/dist/components/SelectContent.vue +40 -0
- package/dist/components/SelectItem.vue +44 -0
- package/dist/components/SelectTrigger.vue +61 -0
- package/dist/components/Switch.vue +43 -0
- package/dist/components/Textarea.vue +55 -0
- package/dist/components/index.d.ts +13 -0
- package/dist/components/index.mjs +13 -0
- package/dist/composables/useButton.d.ts +2 -2
- package/dist/composables/useButton.mjs +14 -41
- package/dist/composables/useCard.d.ts +2 -2
- package/dist/composables/useCard.mjs +5 -18
- package/dist/composables/useCheckbox.d.ts +2 -1
- package/dist/composables/useCheckbox.mjs +11 -44
- package/dist/composables/useFileInput.d.ts +2 -0
- package/dist/composables/useFileInput.mjs +15 -30
- package/dist/composables/useInput.d.ts +2 -2
- package/dist/composables/useInput.mjs +12 -33
- package/dist/composables/useRadio.d.ts +2 -1
- package/dist/composables/useRadio.mjs +10 -42
- package/dist/composables/useSelect.d.ts +3 -0
- package/dist/composables/useSelect.mjs +20 -49
- package/dist/composables/useSwitch.d.ts +2 -1
- package/dist/composables/useSwitch.mjs +10 -43
- package/dist/composables/useTextarea.d.ts +2 -1
- package/dist/composables/useTextarea.mjs +12 -33
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/types/button.d.ts +19 -5
- package/dist/types/card.d.ts +11 -2
- package/dist/types/checkbox.d.ts +15 -6
- package/dist/types/composables.d.ts +33 -5
- package/dist/types/file-input.d.ts +15 -5
- package/dist/types/input.d.ts +17 -6
- package/dist/types/radio.d.ts +14 -6
- package/dist/types/select.d.ts +25 -7
- package/dist/types/switch.d.ts +14 -5
- package/dist/types/textarea.d.ts +18 -7
- package/package.json +1 -9
- package/dist/components/ui/Alert.vue +0 -41
- package/dist/components/ui/AlertDescription.vue +0 -22
- package/dist/components/ui/AlertTitle.vue +0 -22
- package/dist/components/ui/Button.vue +0 -85
- package/dist/components/ui/Card.vue +0 -39
- package/dist/components/ui/CardContent.vue +0 -22
- package/dist/components/ui/CardDescription.vue +0 -22
- package/dist/components/ui/CardFooter.vue +0 -22
- package/dist/components/ui/CardHeader.vue +0 -22
- package/dist/components/ui/CardTitle.vue +0 -22
- package/dist/components/ui/Checkbox.vue +0 -94
- package/dist/components/ui/Dialog.vue +0 -110
- package/dist/components/ui/DialogDescription.vue +0 -22
- package/dist/components/ui/DialogFooter.vue +0 -22
- package/dist/components/ui/DialogHeader.vue +0 -22
- package/dist/components/ui/DialogTitle.vue +0 -22
- package/dist/components/ui/DropdownMenu.vue +0 -32
- package/dist/components/ui/DropdownMenuContent.vue +0 -69
- package/dist/components/ui/DropdownMenuItem.vue +0 -71
- package/dist/components/ui/DropdownMenuLabel.vue +0 -20
- package/dist/components/ui/DropdownMenuSeparator.vue +0 -16
- package/dist/components/ui/DropdownMenuTrigger.vue +0 -38
- package/dist/components/ui/FileInput.vue +0 -153
- package/dist/components/ui/FormError.vue +0 -20
- package/dist/components/ui/FormField.vue +0 -12
- package/dist/components/ui/FormInput.vue +0 -46
- package/dist/components/ui/FormLabel.vue +0 -19
- package/dist/components/ui/FormTextarea.vue +0 -39
- package/dist/components/ui/Input.vue +0 -72
- package/dist/components/ui/Popover.vue +0 -35
- package/dist/components/ui/PopoverContent.vue +0 -66
- package/dist/components/ui/PopoverTrigger.vue +0 -36
- package/dist/components/ui/RadioGroup.vue +0 -47
- package/dist/components/ui/RadioItem.vue +0 -62
- package/dist/components/ui/Select.vue +0 -62
- package/dist/components/ui/SelectContent.vue +0 -55
- package/dist/components/ui/SelectItem.vue +0 -55
- package/dist/components/ui/SelectTrigger.vue +0 -70
- package/dist/components/ui/SelectValue.vue +0 -27
- package/dist/components/ui/Sheet.vue +0 -148
- package/dist/components/ui/SheetDescription.vue +0 -22
- package/dist/components/ui/SheetFooter.vue +0 -22
- package/dist/components/ui/SheetHeader.vue +0 -22
- package/dist/components/ui/SheetTitle.vue +0 -22
- package/dist/components/ui/Switch.vue +0 -63
- package/dist/components/ui/Textarea.vue +0 -73
- package/dist/components/ui/Toast.vue +0 -116
- package/dist/components/ui/Toaster.vue +0 -76
- package/dist/components/ui/Tooltip.vue +0 -41
- package/dist/components/ui/TooltipContent.vue +0 -71
- package/dist/components/ui/TooltipTrigger.vue +0 -39
- package/dist/components/ui/UIProvider.vue +0 -23
- package/dist/components/ui/index.d.ts +0 -52
- package/dist/components/ui/index.mjs +0 -52
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
:disabled="composable.state.value.disabled || composable.state.value.loading"
|
|
4
|
+
:type="composable.state.value.type"
|
|
5
|
+
v-bind="composable.aria_attributes.value"
|
|
6
|
+
@click="handleClick"
|
|
7
|
+
>
|
|
8
|
+
<slot />
|
|
9
|
+
</button>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import { toRef } from "vue"
|
|
14
|
+
import { useButton } from "../../composables/useButton"
|
|
15
|
+
import type { ButtonProps } from "../../types/button"
|
|
16
|
+
|
|
17
|
+
const props = withDefaults(defineProps<ButtonProps>(), {
|
|
18
|
+
variant: "primary",
|
|
19
|
+
size: "md",
|
|
20
|
+
disabled: false,
|
|
21
|
+
loading: false,
|
|
22
|
+
type: "button"
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const emit = defineEmits<{
|
|
26
|
+
click: [event: MouseEvent]
|
|
27
|
+
}>()
|
|
28
|
+
|
|
29
|
+
const composable = useButton(toRef(() => props))
|
|
30
|
+
|
|
31
|
+
const handleClick = (event: MouseEvent) => {
|
|
32
|
+
if (!composable.state.value.disabled && !composable.state.value.loading) {
|
|
33
|
+
emit("click", event)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
</script>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div>
|
|
3
|
+
<slot />
|
|
4
|
+
</div>
|
|
5
|
+
</template>
|
|
6
|
+
|
|
7
|
+
<script setup lang="ts">
|
|
8
|
+
import { toRef } from "vue"
|
|
9
|
+
import { useCard } from "../../composables/useCard"
|
|
10
|
+
import type { CardProps } from "../../types/card"
|
|
11
|
+
|
|
12
|
+
const props = withDefaults(defineProps<CardProps>(), {
|
|
13
|
+
padding: "md"
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// Composable is available for state access if needed
|
|
17
|
+
const composable = useCard(toRef(() => props))
|
|
18
|
+
|
|
19
|
+
// Export state for external access
|
|
20
|
+
defineExpose({
|
|
21
|
+
state: composable.state
|
|
22
|
+
})
|
|
23
|
+
</script>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
:disabled="composable.state.value.disabled"
|
|
5
|
+
:data-state="dataState"
|
|
6
|
+
v-bind="composable.aria_attributes.value"
|
|
7
|
+
@click="handleClick"
|
|
8
|
+
@keydown.space.prevent="handleClick"
|
|
9
|
+
>
|
|
10
|
+
<slot :checked="model" :indeterminate="composable.state.value.indeterminate" />
|
|
11
|
+
</button>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
import { toRef, computed } from "vue"
|
|
16
|
+
import { useCheckbox } from "../../composables/useCheckbox"
|
|
17
|
+
import type { CheckboxProps } from "../../types/checkbox"
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(defineProps<CheckboxProps>(), {
|
|
20
|
+
size: "md",
|
|
21
|
+
disabled: false,
|
|
22
|
+
indeterminate: false
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const model = defineModel<boolean>({ default: false })
|
|
26
|
+
|
|
27
|
+
const emit = defineEmits<{
|
|
28
|
+
change: [checked: boolean]
|
|
29
|
+
}>()
|
|
30
|
+
|
|
31
|
+
const composable = useCheckbox(toRef(() => props), model)
|
|
32
|
+
|
|
33
|
+
const dataState = computed(() => {
|
|
34
|
+
if (composable.state.value.indeterminate) return "indeterminate"
|
|
35
|
+
return model.value ? "checked" : "unchecked"
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const handleClick = () => {
|
|
39
|
+
if (!composable.state.value.disabled) {
|
|
40
|
+
model.value = !model.value
|
|
41
|
+
emit("change", model.value)
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
</script>
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<Teleport to="body">
|
|
3
|
+
<div
|
|
4
|
+
v-if="is_open"
|
|
5
|
+
role="dialog"
|
|
6
|
+
aria-modal="true"
|
|
7
|
+
:aria-labelledby="titleId"
|
|
8
|
+
:aria-describedby="descriptionId"
|
|
9
|
+
>
|
|
10
|
+
<!-- Overlay -->
|
|
11
|
+
<div
|
|
12
|
+
@click="handleOverlayClick"
|
|
13
|
+
@keydown.escape="close"
|
|
14
|
+
/>
|
|
15
|
+
<!-- Content -->
|
|
16
|
+
<div
|
|
17
|
+
ref="content_ref"
|
|
18
|
+
tabindex="-1"
|
|
19
|
+
@keydown.escape="close"
|
|
20
|
+
>
|
|
21
|
+
<slot :close="close" />
|
|
22
|
+
</div>
|
|
23
|
+
</div>
|
|
24
|
+
</Teleport>
|
|
25
|
+
</template>
|
|
26
|
+
|
|
27
|
+
<script setup lang="ts">
|
|
28
|
+
import { ref, watch, onMounted, onUnmounted, provide } from "vue"
|
|
29
|
+
import { useDialog } from "../../composables/useDialog"
|
|
30
|
+
import { DIALOG_KEY } from "../../types/dialog"
|
|
31
|
+
import type { DialogContext } from "../../types/dialog"
|
|
32
|
+
|
|
33
|
+
interface HeadlessDialogProps {
|
|
34
|
+
closeOnOverlay?: boolean
|
|
35
|
+
closeOnEscape?: boolean
|
|
36
|
+
titleId?: string
|
|
37
|
+
descriptionId?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const props = withDefaults(defineProps<HeadlessDialogProps>(), {
|
|
41
|
+
closeOnOverlay: true,
|
|
42
|
+
closeOnEscape: true
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
const model = defineModel<boolean>("open", { default: false })
|
|
46
|
+
|
|
47
|
+
const content_ref = ref<HTMLElement | null>(null)
|
|
48
|
+
const dialog = useDialog()
|
|
49
|
+
const { is_open, open, close, toggle } = dialog
|
|
50
|
+
|
|
51
|
+
// Sync with v-model
|
|
52
|
+
watch(model, (value) => {
|
|
53
|
+
if (value) {
|
|
54
|
+
open()
|
|
55
|
+
} else {
|
|
56
|
+
close()
|
|
57
|
+
}
|
|
58
|
+
}, { immediate: true })
|
|
59
|
+
|
|
60
|
+
watch(is_open, (value) => {
|
|
61
|
+
model.value = value
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const handleOverlayClick = () => {
|
|
65
|
+
if (props.closeOnOverlay) {
|
|
66
|
+
close()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
71
|
+
if (event.key === "Escape" && props.closeOnEscape && is_open.value) {
|
|
72
|
+
close()
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Provide context for child components
|
|
77
|
+
const context: DialogContext = {
|
|
78
|
+
is_open,
|
|
79
|
+
close,
|
|
80
|
+
titleId: props.titleId,
|
|
81
|
+
descriptionId: props.descriptionId
|
|
82
|
+
}
|
|
83
|
+
provide(DIALOG_KEY, context)
|
|
84
|
+
|
|
85
|
+
onMounted(() => {
|
|
86
|
+
document.addEventListener("keydown", handleKeyDown)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
onUnmounted(() => {
|
|
90
|
+
document.removeEventListener("keydown", handleKeyDown)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
defineExpose({
|
|
94
|
+
is_open,
|
|
95
|
+
open,
|
|
96
|
+
close,
|
|
97
|
+
toggle
|
|
98
|
+
})
|
|
99
|
+
</script>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<input
|
|
3
|
+
:type="composable.state.value.type"
|
|
4
|
+
:value="modelValue"
|
|
5
|
+
:disabled="composable.state.value.disabled"
|
|
6
|
+
:readonly="composable.state.value.readonly"
|
|
7
|
+
:placeholder="placeholder"
|
|
8
|
+
:id="id"
|
|
9
|
+
v-bind="composable.aria_attributes.value"
|
|
10
|
+
@input="handleInput"
|
|
11
|
+
@focus="composable.handleFocus"
|
|
12
|
+
@blur="composable.handleBlur"
|
|
13
|
+
/>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import { toRef } from "vue"
|
|
18
|
+
import { useInput } from "../../composables/useInput"
|
|
19
|
+
import type { InputType, InputSize } from "../../types/input"
|
|
20
|
+
|
|
21
|
+
const props = withDefaults(defineProps<{
|
|
22
|
+
type?: InputType
|
|
23
|
+
size?: InputSize
|
|
24
|
+
placeholder?: string
|
|
25
|
+
disabled?: boolean
|
|
26
|
+
readonly?: boolean
|
|
27
|
+
error?: string
|
|
28
|
+
error_id?: string
|
|
29
|
+
id?: string
|
|
30
|
+
modelValue?: string
|
|
31
|
+
}>(), {
|
|
32
|
+
type: "text",
|
|
33
|
+
size: "md",
|
|
34
|
+
disabled: false,
|
|
35
|
+
readonly: false
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
const emit = defineEmits<{
|
|
39
|
+
"update:modelValue": [value: string]
|
|
40
|
+
}>()
|
|
41
|
+
|
|
42
|
+
const composable = useInput(toRef(() => props))
|
|
43
|
+
|
|
44
|
+
const handleInput = (event: Event) => {
|
|
45
|
+
const target = event.target as HTMLInputElement
|
|
46
|
+
emit("update:modelValue", target.value)
|
|
47
|
+
}
|
|
48
|
+
</script>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
role="radiogroup"
|
|
4
|
+
:aria-orientation="orientation"
|
|
5
|
+
>
|
|
6
|
+
<slot />
|
|
7
|
+
</div>
|
|
8
|
+
</template>
|
|
9
|
+
|
|
10
|
+
<script setup lang="ts">
|
|
11
|
+
import { provide, computed } from "vue"
|
|
12
|
+
import type { RadioGroupProps, RadioGroupContext } from "../../types/radio"
|
|
13
|
+
import { RADIO_GROUP_KEY } from "../../types/radio"
|
|
14
|
+
|
|
15
|
+
const props = withDefaults(defineProps<RadioGroupProps>(), {
|
|
16
|
+
disabled: false,
|
|
17
|
+
orientation: "vertical"
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
const model = defineModel<string>({ default: "" })
|
|
21
|
+
|
|
22
|
+
const updateValue = (value: string) => {
|
|
23
|
+
if (!props.disabled) {
|
|
24
|
+
model.value = value
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const context: RadioGroupContext = {
|
|
29
|
+
model_value: model,
|
|
30
|
+
disabled: computed(() => props.disabled),
|
|
31
|
+
updateValue
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
provide(RADIO_GROUP_KEY, context)
|
|
35
|
+
</script>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
:disabled="composable.state.value.disabled"
|
|
5
|
+
:data-state="composable.state.value.checked ? 'checked' : 'unchecked'"
|
|
6
|
+
v-bind="composable.aria_attributes.value"
|
|
7
|
+
@click="handleClick"
|
|
8
|
+
@keydown="handleKeyDown"
|
|
9
|
+
>
|
|
10
|
+
<slot :checked="composable.state.value.checked" :disabled="composable.state.value.disabled" />
|
|
11
|
+
</button>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<script setup lang="ts">
|
|
15
|
+
import { inject, toRef } from "vue"
|
|
16
|
+
import { useRadioItem } from "../../composables/useRadio"
|
|
17
|
+
import type { RadioItemProps, RadioGroupContext } from "../../types/radio"
|
|
18
|
+
import { RADIO_GROUP_KEY } from "../../types/radio"
|
|
19
|
+
import { COMPONENT_ERRORS } from "../../constants/errors"
|
|
20
|
+
|
|
21
|
+
const props = withDefaults(defineProps<RadioItemProps>(), {
|
|
22
|
+
size: "md",
|
|
23
|
+
disabled: false
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const context = inject<RadioGroupContext | null>(RADIO_GROUP_KEY, null)
|
|
27
|
+
|
|
28
|
+
if (!context) {
|
|
29
|
+
throw new Error(COMPONENT_ERRORS.PROVIDER_NOT_FOUND)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const composable = useRadioItem(toRef(() => props))
|
|
33
|
+
|
|
34
|
+
const handleClick = () => {
|
|
35
|
+
if (!composable.state.value.disabled) {
|
|
36
|
+
context.updateValue(props.value)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
41
|
+
if (composable.state.value.disabled) return
|
|
42
|
+
|
|
43
|
+
switch (event.key) {
|
|
44
|
+
case "Enter":
|
|
45
|
+
case " ":
|
|
46
|
+
event.preventDefault()
|
|
47
|
+
context.updateValue(props.value)
|
|
48
|
+
break
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
defineExpose({
|
|
53
|
+
state: composable.state
|
|
54
|
+
})
|
|
55
|
+
</script>
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div ref="root_ref">
|
|
3
|
+
<slot
|
|
4
|
+
:is_open="context.is_open.value"
|
|
5
|
+
:selected_value="model"
|
|
6
|
+
:open="context.open"
|
|
7
|
+
:close="context.close"
|
|
8
|
+
:toggle="context.toggle"
|
|
9
|
+
/>
|
|
10
|
+
</div>
|
|
11
|
+
</template>
|
|
12
|
+
|
|
13
|
+
<script setup lang="ts">
|
|
14
|
+
import { ref, provide, watch, onMounted, onUnmounted, computed } from "vue"
|
|
15
|
+
import type { SelectProps, SelectContext } from "../../types/select"
|
|
16
|
+
import { SELECT_KEY } from "../../types/select"
|
|
17
|
+
|
|
18
|
+
const props = withDefaults(defineProps<SelectProps>(), {
|
|
19
|
+
size: "md",
|
|
20
|
+
disabled: false,
|
|
21
|
+
placeholder: ""
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const model = defineModel<string>({ default: "" })
|
|
25
|
+
|
|
26
|
+
const root_ref = ref<HTMLElement | null>(null)
|
|
27
|
+
const trigger_ref = ref<HTMLElement | null>(null)
|
|
28
|
+
const is_open = ref(false)
|
|
29
|
+
|
|
30
|
+
const open = () => {
|
|
31
|
+
if (!props.disabled) {
|
|
32
|
+
is_open.value = true
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const close = () => {
|
|
37
|
+
is_open.value = false
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const toggle = () => {
|
|
41
|
+
if (is_open.value) {
|
|
42
|
+
close()
|
|
43
|
+
} else {
|
|
44
|
+
open()
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const updateValue = (value: string) => {
|
|
49
|
+
model.value = value
|
|
50
|
+
close()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const setTriggerRef = (element: HTMLElement | null) => {
|
|
54
|
+
trigger_ref.value = element
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const handleClickOutside = (event: MouseEvent) => {
|
|
58
|
+
if (!is_open.value) return
|
|
59
|
+
const target = event.target as Node
|
|
60
|
+
if (root_ref.value && !root_ref.value.contains(target)) {
|
|
61
|
+
close()
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const context: SelectContext = {
|
|
66
|
+
model_value: model,
|
|
67
|
+
is_open,
|
|
68
|
+
disabled: computed(() => props.disabled),
|
|
69
|
+
size: computed(() => props.size ?? "md"),
|
|
70
|
+
placeholder: computed(() => props.placeholder ?? ""),
|
|
71
|
+
trigger_ref,
|
|
72
|
+
updateValue,
|
|
73
|
+
open,
|
|
74
|
+
close,
|
|
75
|
+
toggle,
|
|
76
|
+
setTriggerRef
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
provide(SELECT_KEY, context)
|
|
80
|
+
|
|
81
|
+
onMounted(() => {
|
|
82
|
+
document.addEventListener("mousedown", handleClickOutside)
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
onUnmounted(() => {
|
|
86
|
+
document.removeEventListener("mousedown", handleClickOutside)
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
defineExpose({
|
|
90
|
+
is_open,
|
|
91
|
+
open,
|
|
92
|
+
close,
|
|
93
|
+
toggle
|
|
94
|
+
})
|
|
95
|
+
</script>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
v-if="composable.state.value.is_open"
|
|
4
|
+
role="listbox"
|
|
5
|
+
tabindex="-1"
|
|
6
|
+
@keydown="handleKeyDown"
|
|
7
|
+
>
|
|
8
|
+
<slot />
|
|
9
|
+
</div>
|
|
10
|
+
</template>
|
|
11
|
+
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import { inject } from "vue"
|
|
14
|
+
import { useSelectContent } from "../../composables/useSelect"
|
|
15
|
+
import type { SelectContext } from "../../types/select"
|
|
16
|
+
import { SELECT_KEY } from "../../types/select"
|
|
17
|
+
import { COMPONENT_ERRORS } from "../../constants/errors"
|
|
18
|
+
|
|
19
|
+
const context = inject<SelectContext | null>(SELECT_KEY, null)
|
|
20
|
+
|
|
21
|
+
if (!context) {
|
|
22
|
+
throw new Error(COMPONENT_ERRORS.PROVIDER_NOT_FOUND)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const composable = useSelectContent()
|
|
26
|
+
|
|
27
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
28
|
+
switch (event.key) {
|
|
29
|
+
case "Escape":
|
|
30
|
+
event.preventDefault()
|
|
31
|
+
context.close()
|
|
32
|
+
context.trigger_ref.value?.focus()
|
|
33
|
+
break
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
defineExpose({
|
|
38
|
+
state: composable.state
|
|
39
|
+
})
|
|
40
|
+
</script>
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div
|
|
3
|
+
role="option"
|
|
4
|
+
:aria-selected="composable.state.value.selected"
|
|
5
|
+
:aria-disabled="composable.state.value.disabled"
|
|
6
|
+
:data-state="composable.state.value.selected ? 'selected' : undefined"
|
|
7
|
+
:data-disabled="composable.state.value.disabled ? '' : undefined"
|
|
8
|
+
@click="handleClick"
|
|
9
|
+
@keydown.enter="handleClick"
|
|
10
|
+
@keydown.space.prevent="handleClick"
|
|
11
|
+
>
|
|
12
|
+
<slot :selected="composable.state.value.selected" :disabled="composable.state.value.disabled" />
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import { inject, toRef } from "vue"
|
|
18
|
+
import { useSelectItem } from "../../composables/useSelect"
|
|
19
|
+
import type { SelectContext, SelectItemProps } from "../../types/select"
|
|
20
|
+
import { SELECT_KEY } from "../../types/select"
|
|
21
|
+
import { COMPONENT_ERRORS } from "../../constants/errors"
|
|
22
|
+
|
|
23
|
+
const props = withDefaults(defineProps<SelectItemProps>(), {
|
|
24
|
+
disabled: false
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const context = inject<SelectContext | null>(SELECT_KEY, null)
|
|
28
|
+
|
|
29
|
+
if (!context) {
|
|
30
|
+
throw new Error(COMPONENT_ERRORS.PROVIDER_NOT_FOUND)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const composable = useSelectItem(toRef(() => ({ value: props.value, disabled: props.disabled })))
|
|
34
|
+
|
|
35
|
+
const handleClick = () => {
|
|
36
|
+
if (!composable.state.value.disabled) {
|
|
37
|
+
context.updateValue(props.value)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
defineExpose({
|
|
42
|
+
state: composable.state
|
|
43
|
+
})
|
|
44
|
+
</script>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
ref="trigger_element"
|
|
4
|
+
type="button"
|
|
5
|
+
:disabled="composable.state.value.disabled"
|
|
6
|
+
:aria-expanded="composable.state.value.is_open"
|
|
7
|
+
aria-haspopup="listbox"
|
|
8
|
+
@click="handleClick"
|
|
9
|
+
@keydown="handleKeyDown"
|
|
10
|
+
>
|
|
11
|
+
<slot :is_open="composable.state.value.is_open" :disabled="composable.state.value.disabled" />
|
|
12
|
+
</button>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup lang="ts">
|
|
16
|
+
import { ref, inject, onMounted } from "vue"
|
|
17
|
+
import { useSelectTrigger } from "../../composables/useSelect"
|
|
18
|
+
import type { SelectContext } from "../../types/select"
|
|
19
|
+
import { SELECT_KEY } from "../../types/select"
|
|
20
|
+
import { COMPONENT_ERRORS } from "../../constants/errors"
|
|
21
|
+
|
|
22
|
+
const trigger_element = ref<HTMLElement | null>(null)
|
|
23
|
+
const context = inject<SelectContext | null>(SELECT_KEY, null)
|
|
24
|
+
|
|
25
|
+
if (!context) {
|
|
26
|
+
throw new Error(COMPONENT_ERRORS.PROVIDER_NOT_FOUND)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const composable = useSelectTrigger()
|
|
30
|
+
|
|
31
|
+
const handleClick = () => {
|
|
32
|
+
if (!composable.state.value.disabled) {
|
|
33
|
+
context.toggle()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
38
|
+
if (composable.state.value.disabled) return
|
|
39
|
+
|
|
40
|
+
switch (event.key) {
|
|
41
|
+
case "Enter":
|
|
42
|
+
case " ":
|
|
43
|
+
case "ArrowDown":
|
|
44
|
+
event.preventDefault()
|
|
45
|
+
context.open()
|
|
46
|
+
break
|
|
47
|
+
case "ArrowUp":
|
|
48
|
+
event.preventDefault()
|
|
49
|
+
context.open()
|
|
50
|
+
break
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
onMounted(() => {
|
|
55
|
+
context.setTriggerRef(trigger_element.value)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
defineExpose({
|
|
59
|
+
state: composable.state
|
|
60
|
+
})
|
|
61
|
+
</script>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<button
|
|
3
|
+
type="button"
|
|
4
|
+
:disabled="composable.state.value.disabled"
|
|
5
|
+
:data-state="model ? 'checked' : 'unchecked'"
|
|
6
|
+
v-bind="composable.aria_attributes.value"
|
|
7
|
+
@click="handleClick"
|
|
8
|
+
>
|
|
9
|
+
<slot :checked="model">
|
|
10
|
+
<span :data-state="model ? 'checked' : 'unchecked'" />
|
|
11
|
+
</slot>
|
|
12
|
+
</button>
|
|
13
|
+
</template>
|
|
14
|
+
|
|
15
|
+
<script setup lang="ts">
|
|
16
|
+
import { toRef } from "vue"
|
|
17
|
+
import { useSwitch } from "../../composables/useSwitch"
|
|
18
|
+
import type { SwitchProps } from "../../types/switch"
|
|
19
|
+
|
|
20
|
+
const props = withDefaults(defineProps<SwitchProps>(), {
|
|
21
|
+
size: "md",
|
|
22
|
+
disabled: false
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
const model = defineModel<boolean>({ default: false })
|
|
26
|
+
|
|
27
|
+
const emit = defineEmits<{
|
|
28
|
+
change: [checked: boolean]
|
|
29
|
+
}>()
|
|
30
|
+
|
|
31
|
+
const composable = useSwitch(toRef(() => props), model)
|
|
32
|
+
|
|
33
|
+
const handleClick = () => {
|
|
34
|
+
if (!composable.state.value.disabled) {
|
|
35
|
+
model.value = !model.value
|
|
36
|
+
emit("change", model.value)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
defineExpose({
|
|
41
|
+
state: composable.state
|
|
42
|
+
})
|
|
43
|
+
</script>
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<textarea
|
|
3
|
+
:value="modelValue"
|
|
4
|
+
:disabled="composable.state.value.disabled"
|
|
5
|
+
:readonly="composable.state.value.readonly"
|
|
6
|
+
:placeholder="placeholder"
|
|
7
|
+
:rows="rows"
|
|
8
|
+
:id="id"
|
|
9
|
+
v-bind="composable.aria_attributes.value"
|
|
10
|
+
@input="handleInput"
|
|
11
|
+
@focus="composable.handleFocus"
|
|
12
|
+
@blur="composable.handleBlur"
|
|
13
|
+
/>
|
|
14
|
+
</template>
|
|
15
|
+
|
|
16
|
+
<script setup lang="ts">
|
|
17
|
+
import { toRef } from "vue"
|
|
18
|
+
import { useTextarea } from "../../composables/useTextarea"
|
|
19
|
+
import type { TextareaSize, TextareaResize } from "../../types/textarea"
|
|
20
|
+
|
|
21
|
+
const props = withDefaults(defineProps<{
|
|
22
|
+
size?: TextareaSize
|
|
23
|
+
placeholder?: string
|
|
24
|
+
disabled?: boolean
|
|
25
|
+
readonly?: boolean
|
|
26
|
+
error?: string
|
|
27
|
+
error_id?: string
|
|
28
|
+
rows?: number
|
|
29
|
+
resize?: TextareaResize
|
|
30
|
+
modelValue?: string
|
|
31
|
+
id?: string
|
|
32
|
+
}>(), {
|
|
33
|
+
size: "md",
|
|
34
|
+
disabled: false,
|
|
35
|
+
readonly: false,
|
|
36
|
+
rows: 3,
|
|
37
|
+
resize: "vertical"
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
const emit = defineEmits<{
|
|
41
|
+
"update:modelValue": [value: string]
|
|
42
|
+
}>()
|
|
43
|
+
|
|
44
|
+
const composable = useTextarea(toRef(() => props))
|
|
45
|
+
|
|
46
|
+
const handleInput = (event: Event) => {
|
|
47
|
+
const target = event.target as HTMLTextAreaElement
|
|
48
|
+
emit("update:modelValue", target.value)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
defineExpose({
|
|
52
|
+
state: composable.state,
|
|
53
|
+
is_focused: composable.is_focused
|
|
54
|
+
})
|
|
55
|
+
</script>
|